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