1 //
2 // Copyright (c) 2010-2022 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System;
9 using System.Linq;
10 using System.IO;
11 using System.Threading;
12 using System.Collections;
13 using System.Collections.Generic;
14 using Antmicro.Renode.Core;
15 using Antmicro.Renode.Exceptions;
16 using Antmicro.Renode.Logging;
17 using Antmicro.Renode.Time;
18 using Antmicro.Renode.Utilities;
19 
20 namespace Antmicro.Renode.Peripherals.Sensors
21 {
22     public class SensorSamplesFifo<T> where T : SensorSample, new()
23     {
SensorSamplesFifo()24         public SensorSamplesFifo()
25         {
26             samplesFifo = new Queue<T>();
27         }
28 
DumpOldestSample()29         public void DumpOldestSample()
30         {
31             lock(samplesFifo)
32             {
33                 samplesFifo.TryDequeue<T>(out _);
34             }
35         }
36 
FeedSample(T sample)37         public virtual void FeedSample(T sample)
38         {
39             lock(samplesFifo)
40             {
41                 samplesFifo.Enqueue(sample);
42             }
43         }
44 
FeedSamplesPeriodically(IMachine machine, IPeripheral owner, string name, string delayString, uint frequency, IEnumerable<T> samples)45         public IManagedThread FeedSamplesPeriodically(IMachine machine, IPeripheral owner, string name, string delayString, uint frequency, IEnumerable<T> samples)
46         {
47             if(!TimeInterval.TryParse(delayString, out var delay))
48             {
49                 throw new RecoverableException($"Invalid delay: {delayString}");
50             }
51 
52             return FeedSamplesInner(machine, owner, name, delay, frequency, samples, true);
53         }
54 
FeedSamplesFromBinaryFile(IMachine machine, IPeripheral owner, string name, string path, string delayString, Func<List<decimal>, T> sampleConstructor = null, Action onFinish = null)55         public IManagedThread FeedSamplesFromBinaryFile(IMachine machine, IPeripheral owner, string name, string path, string delayString, Func<List<decimal>, T> sampleConstructor = null, Action onFinish = null)
56         {
57             if(sampleConstructor == null)
58             {
59                 sampleConstructor = values =>
60                 {
61                     var sample = new T();
62                     sample.Load(values);
63                     return sample;
64                 };
65             }
66 
67             if(!TimeInterval.TryParse(delayString, out var delay))
68             {
69                 throw new RecoverableException($"Invalid delay: {delayString}");
70             }
71             TimeInterval? firstPacketStartTime = null;
72 
73             var result = new ManagedThreadsContainer();
74             var packets = SensorSamplesPacket<T>.ParseFile(path, sampleConstructor);
75             foreach(var packet in packets)
76             {
77                 firstPacketStartTime = firstPacketStartTime ?? packet.StartTime;
78                 var packetDelay = packet.StartTime - firstPacketStartTime.Value + delay;
79                 var feeder = FeedSamplesInner(machine, owner, name, packetDelay, packet.Frequency, packet.Samples, false, onFinish);
80                 result.Add(feeder);
81             }
82 
83             return result;
84         }
85 
FeedSamplesFromFile(string path, Func<string[], T> sampleConstructor = null)86         public void FeedSamplesFromFile(string path, Func<string[], T> sampleConstructor = null)
87         {
88             if(sampleConstructor == null)
89             {
90                 sampleConstructor = stringArray =>
91                 {
92                     var sample = new T();
93                     return sample.TryLoad(stringArray) ? sample : null;
94                 };
95             }
96 
97             lock(samplesFifo)
98             {
99                 var samples = ParseSamplesFile(path, sampleConstructor);
100                 samplesFifo.EnqueueRange(samples);
101             }
102         }
103 
Reset()104         public virtual void Reset()
105         {
106             currentSample = null;
107             samplesFifo.Clear();
108         }
109 
TryDequeueNewSample()110         public virtual bool TryDequeueNewSample()
111         {
112             return samplesFifo.TryDequeue(out currentSample);
113         }
114 
115         public virtual T Sample => currentSample ?? DefaultSample;
116 
117         public T DefaultSample { get; } = new T();
118 
119         public uint SamplesCount => (uint)samplesFifo.Count;
120 
FeedSamplesInner(IMachine machine, IPeripheral owner, string name, TimeInterval startDelay, uint frequency, IEnumerable<T> samples, bool repeatMode = false, Action onFinish = null)121         private IManagedThread FeedSamplesInner(IMachine machine, IPeripheral owner, string name, TimeInterval startDelay, uint frequency, IEnumerable<T> samples, bool repeatMode = false, Action onFinish = null)
122         {
123             var samplesBuffer = new SamplesBuffer(samples, autoRewind: repeatMode);
124 
125             owner.Log(LogLevel.Noisy, "{0}: {1} samples will be fed at {2} Hz starting at {3:c}", name, samplesBuffer.TotalSamplesCount, frequency, startDelay.ToTimeSpan());
126 
127             Action feedSample = () =>
128             {
129                 if(!samplesBuffer.TryGetNextSample(out var sample))
130                 {
131                     return;
132                 }
133                 owner.Log(LogLevel.Noisy, "{0} {1}: Feeding sample {2}/{3}: {4}", machine.ElapsedVirtualTime.TimeElapsed, name, samplesBuffer.CurrentSampleIndex, samplesBuffer.TotalSamplesCount, sample);
134                 FeedSample(sample);
135             };
136 
137             Func<bool> stopCondition = () =>
138             {
139                 if(samplesBuffer.IsFinished)
140                 {
141                     owner.Log(LogLevel.Noisy, "{0} {1}: All samples fed", machine.ElapsedVirtualTime.TimeElapsed, name);
142                     onFinish?.Invoke();
143                     return true;
144                 }
145                 return false;
146             };
147 
148             var feederThread = machine.ObtainManagedThread(feedSample, frequency, name + " sample loader", owner, stopCondition);
149             feederThread.StartDelayed(startDelay);
150             return feederThread;
151         }
152 
ParseSamplesFile(string path, Func<string[], T> sampleConstructor)153         private IEnumerable<T> ParseSamplesFile(string path, Func<string[], T> sampleConstructor)
154         {
155             var localQueue = new Queue<T>();
156             var lineNumber = 0;
157 
158             try
159             {
160                 using(var reader = File.OpenText(path))
161                 {
162                     var line = "";
163                     while((line = reader.ReadLine()) != null)
164                     {
165                         ++lineNumber;
166 
167                         if(line.Trim().StartsWith("#"))
168                         {
169                             // this is a comment, just ignore
170                             continue;
171                         }
172 
173                         var numbers = line.Split(new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(a => a.Trim()).ToArray();
174 
175                         var sample = sampleConstructor(numbers);
176                         if(sample == null)
177                         {
178                             throw new RecoverableException($"Wrong data file format at line {lineNumber}: {line}");
179                         }
180                         localQueue.Enqueue(sample);
181                     }
182                 }
183             }
184             catch(Exception e)
185             {
186                 if(e is RecoverableException)
187                 {
188                     throw;
189                 }
190 
191                 throw new RecoverableException($"There was a problem when reading samples file: {e.Message}");
192             }
193 
194             return localQueue;
195         }
196 
197         private T currentSample;
198         private readonly Queue<T> samplesFifo;
199 
200         private class ManagedThreadsContainer : IManagedThread
201         {
Add(IManagedThread t)202             public void Add(IManagedThread t)
203             {
204                 threads.Add(t);
205             }
206 
Dispose()207             public void Dispose()
208             {
209                 foreach(var t in threads)
210                 {
211                     t.Dispose();
212                 }
213             }
214 
Start()215             public void Start()
216             {
217                 foreach(var t in threads)
218                 {
219                     t.Start();
220                 }
221             }
222 
StartDelayed(TimeInterval i)223             public void StartDelayed(TimeInterval i)
224             {
225                 foreach(var t in threads)
226                 {
227                     t.StartDelayed(i);
228                 }
229             }
230 
Stop()231             public void Stop()
232             {
233                 foreach(var t in threads)
234                 {
235                     t.Stop();
236                 }
237             }
238 
239             public uint Frequency
240             {
241                 // We assume, that all threads have the same frequency
242                 get => threads[0].Frequency;
243                 set
244                 {
245                     foreach(var t in threads)
246                     {
247                         t.Frequency = value;
248                     }
249                 }
250             }
251 
252             private readonly List<IManagedThread> threads = new List<IManagedThread>();
253         }
254 
255         private class SamplesBuffer
256         {
SamplesBuffer(IEnumerable<T> samples, bool autoRewind)257             public SamplesBuffer(IEnumerable<T> samples, bool autoRewind)
258             {
259                 internalSamples = new List<T>(samples);
260                 this.autoRewind = autoRewind;
261             }
262 
TryGetNextSample(out T sample)263             public bool TryGetNextSample(out T sample)
264             {
265                 if(IsFinished)
266                 {
267                     sample = default(T);
268                     return false;
269                 }
270 
271                 // if `autoRewind` was false, IsFinished would return `false`
272                 if(CurrentSampleIndex == internalSamples.Count)
273                 {
274                     CurrentSampleIndex = 0;
275                 }
276 
277                 sample = internalSamples[CurrentSampleIndex];
278                 CurrentSampleIndex++;
279                 return true;
280             }
281 
282             public bool IsFinished => internalSamples.Count == 0 || (CurrentSampleIndex == internalSamples.Count && !autoRewind);
283 
284             public int TotalSamplesCount => internalSamples.Count;
285 
286             public int CurrentSampleIndex { get; private set; }
287 
288             private readonly bool autoRewind;
289             private readonly List<T> internalSamples;
290         }
291     }
292 }
293