1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using Antmicro.Migrant;
9 using System.IO;
10 using System;
11 using System.Collections.Generic;
12 using System.IO.Compression;
13 using System.Text;
14 using Antmicro.Renode.Exceptions;
15 using IronPython.Runtime;
16 using Antmicro.Renode.Peripherals.Python;
17 using Antmicro.Renode.Utilities;
18 using System.Diagnostics;
19 using System.Threading;
20 using System.Reflection;
21 using Antmicro.Renode.Logging;
22 using Antmicro.Renode.UserInterface;
23 using Antmicro.Renode.Time;
24 
25 namespace Antmicro.Renode.Core
26 {
27     public sealed class EmulationManager
28     {
29         public static ITimeDomain ExternalWorld { get; private set; }
30 
31         public static EmulationManager Instance { get; private set; }
32 
EmulationManager()33         static EmulationManager()
34         {
35             ExternalWorld = new ExternalWorldTimeDomain();
36             RebuildInstance();
37         }
38 
39         [HideInMonitor]
RebuildInstance()40         public static void RebuildInstance()
41         {
42             Instance = new EmulationManager();
43         }
44 
45         public ProgressMonitor ProgressMonitor { get; private set; }
46 
47         public Emulation CurrentEmulation
48         {
49             get
50             {
51                 return currentEmulation;
52             }
53             set
54             {
55                 lock(currentEmulationLock)
56                 {
57                     currentEmulation.Dispose();
58                     currentEmulation = value;
59                     InvokeEmulationChanged();
60 
61                     if(profilerPathPrefix != null)
62                     {
63                         currentEmulation.MachineAdded += EnableProfilerInMachine;
64                     }
65                 }
66             }
67         }
68 
EnableProfilerGlobally(WriteFilePath pathPrefix)69         public void EnableProfilerGlobally(WriteFilePath pathPrefix)
70         {
71             profilerPathPrefix = pathPrefix;
72 
73             CurrentEmulation.MachineAdded -= EnableProfilerInMachine;
74             CurrentEmulation.MachineAdded += EnableProfilerInMachine;
75 
76             foreach(var machine in CurrentEmulation.Machines)
77             {
78                 EnableProfilerInMachine(machine);
79             }
80         }
81 
EnsureTypeIsLoaded(string name)82         public void EnsureTypeIsLoaded(string name)
83         {
84             // this is a bit hacky - calling `GetTypeByName` forces
85             // TypeManager to load the type into memory making
86             // it accessible for dynamically compiled C# code
87             try
88             {
89                 TypeManager.Instance.GetTypeByName(name);
90             }
91             catch(Exception e)
92             {
93                 throw new RecoverableException($"Unable to load the type `{name}`: {e.Message}");
94             }
95         }
96 
Load(ReadFilePath path)97         public void Load(ReadFilePath path)
98         {
99             using(var fstream = new FileStream(path, FileMode.Open, FileAccess.Read))
100             using(var stream = path.ToString().EndsWith(".gz", StringComparison.InvariantCulture)
101                                 ? (Stream) new GZipStream(fstream, CompressionMode.Decompress)
102                                 : (Stream) fstream)
103             {
104                 var deserializationResult = serializer.TryDeserialize<string>(stream, out var version);
105                 if(deserializationResult != DeserializationResult.OK)
106                 {
107                     throw new RecoverableException($"There was an error when deserializing the emulation: {deserializationResult}\n Underlying exception: {serializer.LastException.Message}\n{serializer.LastException.StackTrace}");
108                 }
109 
110                 deserializationResult = serializer.TryDeserialize<Emulation>(stream, out var emulation);
111                 if(deserializationResult != DeserializationResult.OK)
112                 {
113                     throw new RecoverableException($"There was an error when deserializing the emulation: {deserializationResult}\n Underlying exception: {serializer.LastException.Message}\n{serializer.LastException.StackTrace}");
114                 }
115 
116                 CurrentEmulation = emulation;
117                 CurrentEmulation.BlobManager.Load(stream, fstream.Name);
118 
119                 if(version != VersionString)
120                 {
121                     Logger.Log(LogLevel.Warning, "Version of deserialized emulation ({0}) does not match current one {1}. Things may go awry!", version, VersionString);
122                 }
123             }
124         }
125 
Save(string path)126         public void Save(string path)
127         {
128             try
129             {
130                 using(var stream = new FileStream(path, FileMode.Create))
131                 {
132                     using(CurrentEmulation.ObtainSafeState())
133                     {
134                         try
135                         {
136                             CurrentEmulation.SnapshotTracker.Save(CurrentEmulation.MasterTimeSource.ElapsedVirtualTime, path);
137                             serializer.Serialize(VersionString, stream);
138                             serializer.Serialize(CurrentEmulation, stream);
139                             CurrentEmulation.BlobManager.Save(stream);
140                         }
141                         catch(InvalidOperationException e)
142                         {
143                             var message = string.Format("Error encountered during saving: {0}", e.Message);
144                             if(e is NonSerializableTypeException && serializer.Settings.SerializationMethod == Migrant.Customization.Method.Generated)
145                             {
146                                 message += "\nHint: Set 'serialization-mode = Reflection' in the Renode config file for detailed information.";
147                             }
148                             else if(e is NonSerializableTypeException && e.Data.Contains("nonSerializableObject") && e.Data.Contains("parentsObjects"))
149                             {
150                                 if(TryFindPath(e.Data["nonSerializableObject"], (Dictionary<object, IEnumerable<object>>)e.Data["parentsObjects"], typeof(Emulation), out List<object> parentsPath))
151                                 {
152                                     var pathText = new StringBuilder();
153 
154                                     parentsPath.Reverse();
155                                     foreach(var o in parentsPath)
156                                     {
157                                         pathText.Append(o.GetType().Name);
158                                         pathText.Append(" => ");
159                                     }
160                                     pathText.Remove(pathText.Length - 4, 4);
161                                     pathText.Append("\n");
162 
163                                     message += "The class path that led to it was:\n" + pathText;
164                                 }
165                             }
166 
167                             throw new RecoverableException(message);
168                         }
169                     }
170                 }
171             }
172             catch(Exception)
173             {
174                 File.Delete(path);
175                 throw;
176             }
177         }
178 
Clear()179         public void Clear()
180         {
181             CurrentEmulation = new Emulation();
182         }
183 
StartTimer(string eventName = null)184         public TimerResult StartTimer(string eventName = null)
185         {
186             stopwatch.Reset();
187             stopwatchCounter = 0;
188             var timerResult = new TimerResult {
189                 FromBeginning = TimeSpan.FromTicks(0),
190                 SequenceNumber = stopwatchCounter,
191                 Timestamp = CustomDateTime.Now,
192                 EventName = eventName
193             };
194             stopwatch.Start();
195             return timerResult;
196         }
197 
CurrentTimer(string eventName = null)198         public TimerResult CurrentTimer(string eventName = null)
199         {
200             stopwatchCounter++;
201             return new TimerResult {
202                 FromBeginning = stopwatch.Elapsed,
203                 SequenceNumber = stopwatchCounter,
204                 Timestamp = CustomDateTime.Now,
205                 EventName = eventName
206             };
207         }
208 
StopTimer(string eventName = null)209         public TimerResult StopTimer(string eventName = null)
210         {
211             stopwatchCounter++;
212             var timerResult = new TimerResult {
213                 FromBeginning = stopwatch.Elapsed,
214                 SequenceNumber = stopwatchCounter,
215                 Timestamp = CustomDateTime.Now,
216                 EventName = eventName
217             };
218             stopwatch.Stop();
219             stopwatch.Reset();
220             return timerResult;
221         }
222 
EnableCompiledFilesCache(bool value)223         public void EnableCompiledFilesCache(bool value)
224         {
225             CompiledFilesCache.Enabled = value;
226         }
227 
228         public string VersionString
229         {
230             get
231             {
232                 var entryAssembly = Assembly.GetEntryAssembly();
233                 if(entryAssembly == null)
234                 {
235                     // When running from NUnit in MonoDevelop entryAssembly is null, but we don't care
236                     return string.Empty;
237                 }
238 
239                 try
240                 {
241                     return string.Format("{0}, version {1} ({2})",
242                         entryAssembly.GetName().Name,
243                         entryAssembly.GetName().Version,
244                         ((AssemblyInformationalVersionAttribute)entryAssembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false)[0]).InformationalVersion
245                     );
246                 }
247                 catch(System.Exception)
248                 {
249                     return string.Empty;
250                 }
251 
252             }
253         }
254 
255         public SimpleFileCache CompiledFilesCache { get; } = new SimpleFileCache("compiler-cache", !Emulator.InCIMode && ConfigurationManager.Instance.Get("general", "compiler-cache-enabled", false));
256 
257         public event Action EmulationChanged;
258 
259         public static bool DisableEmulationFilesCleanup = false;
260 
TryFindPath(object obj, Dictionary<object, IEnumerable<object>> parents, Type finalType, out List<object> resultPath)261         private static bool TryFindPath(object obj, Dictionary<object, IEnumerable<object>> parents, Type finalType, out List<object> resultPath)
262         {
263             return TryFindPathInnerRecursive(obj, parents, new List<object>(), finalType, out resultPath);
264         }
265 
TryFindPathInnerRecursive(object obj, Dictionary<object, IEnumerable<object>> parents, List<object> currentPath, Type finalType, out List<object> resultPath)266         private static bool TryFindPathInnerRecursive(object obj, Dictionary<object, IEnumerable<object>> parents, List<object> currentPath, Type finalType, out List<object> resultPath)
267         {
268             currentPath.Add(obj);
269             if(obj.GetType() == finalType)
270             {
271                 resultPath = currentPath;
272                 return true;
273             }
274 
275             if(parents.ContainsKey(obj))
276             {
277                 foreach(var parent in parents[obj])
278                 {
279                     if(!currentPath.Contains(parent))
280                     {
281                         if(TryFindPathInnerRecursive(parent, parents, currentPath, finalType, out resultPath))
282                         {
283                             return true;
284                         }
285                     }
286                 }
287 
288                 currentPath.RemoveAt(currentPath.Count - 1);
289                 resultPath = null;
290                 return false;
291             }
292             else
293             {
294                 resultPath = currentPath;
295                 return false;
296             }
297         }
298 
EmulationManager()299         private EmulationManager()
300         {
301             var serializerMode = ConfigurationManager.Instance.Get("general", "serialization-mode", Antmicro.Migrant.Customization.Method.Generated);
302 
303             var settings = new Antmicro.Migrant.Customization.Settings(serializerMode, serializerMode,
304                 Antmicro.Migrant.Customization.VersionToleranceLevel.AllowGuidChange, disableTypeStamping: true);
305             serializer = new Serializer(settings);
306             serializer.ForObject<PythonDictionary>().SetSurrogate(x => new PythonDictionarySurrogate(x));
307             serializer.ForSurrogate<PythonDictionarySurrogate>().SetObject(x => x.Restore());
308             currentEmulation = new Emulation();
309             ProgressMonitor = new ProgressMonitor();
310             stopwatch = new Stopwatch();
311             currentEmulationLock = new object();
312         }
313 
InvokeEmulationChanged()314         private void InvokeEmulationChanged()
315         {
316             var emulationChanged = EmulationChanged;
317             if(emulationChanged != null)
318             {
319                 emulationChanged();
320             }
321         }
322 
EnableProfilerInMachine(IMachine machine)323         private void EnableProfilerInMachine(IMachine machine)
324         {
325             var profilerPath = new SequencedFilePath($"{profilerPathPrefix}-{CurrentEmulation[machine]}");
326             machine.EnableProfiler(profilerPath);
327         }
328 
329         private int stopwatchCounter;
330         private Stopwatch stopwatch;
331         private readonly Serializer serializer;
332         private Emulation currentEmulation;
333         private readonly object currentEmulationLock;
334         private string profilerPathPrefix;
335 
336         /// <summary>
337         /// Represents external world time domain.
338         /// </summary>
339         /// <remarks>
340         /// Is used as a source of all external, asynchronous input events (e.g., user input on uart analyzer).
341         /// </remarks>
342         private class ExternalWorldTimeDomain : ITimeDomain
343         {
344         }
345     }
346 }
347 
348