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.Linq; 9 using System.Collections.Generic; 10 using AntShell.Commands; 11 using Antmicro.Renode.Time; 12 using Antmicro.Renode.UserInterface.Tokenizer; 13 using Antmicro.Renode.Utilities; 14 using Antmicro.Renode.Utilities.RESD; 15 16 namespace Antmicro.Renode.UserInterface.Commands 17 { 18 public class ResdCommand : Command 19 { PrintHelp(ICommandInteraction writer)20 public override void PrintHelp(ICommandInteraction writer) 21 { 22 base.PrintHelp(writer); 23 writer.WriteLine(); 24 writer.WriteLine("You can use the following commands:"); 25 writer.WriteLine("'resd load NAME PATH'\tloads RESD file under identifier NAME"); 26 writer.WriteLine("'resd unload NAME'\tunloads RESD file with identifier NAME"); 27 writer.WriteLine("'resd list-blocks NAME'\tlist data blocks from RESD file with identifier NAME"); 28 writer.WriteLine("'resd describe-block NAME INDEX'\tshow informations about INDEXth block from RESD with identifier NAME"); 29 writer.WriteLine("'resd get-samples NAME INDEX \"START_TIME\" COUNT'\tlists COUNT samples starting at START_TIME from INDEXth block of RESD with identifier NAME"); 30 writer.WriteLine("'resd get-samples-range NAME INDEX \"START_TIME\" \"DURATION\"'\tlists DURATION samples starting at START_TIME from INDEXth block of RESD with identifier NAME"); 31 writer.WriteLine("'resd get-samples-range NAME INDEX \"START_TIME..END_TIME\"'\tlists samples between START_TIME and END_TIME from INDEXth block of RESD with identifier NAME"); 32 writer.WriteLine("'resd get-prop NAME INDEX PROP'\tread property PROP from INDEXth block of RESD with identifier NAME"); 33 writer.WriteLine($" possible values for PROP are: {RESDPropertyNames}"); 34 writer.WriteLine(); 35 36 writer.WriteLine("Currently loaded RESD files:"); 37 foreach(var item in resdFiles) 38 { 39 var sourceName = item.Value.FilePath ?? "memory"; 40 writer.WriteLine($"{item.Key} @ {sourceName}"); 41 } 42 } 43 44 [Runnable] Run(ICommandInteraction writer, [Values(R)] LiteralToken action, LiteralToken internalName, StringToken filePath)45 public void Run(ICommandInteraction writer, [Values("load")] LiteralToken action, LiteralToken internalName, StringToken filePath) 46 { 47 if(resdFiles.ContainsKey(internalName.Value)) 48 { 49 writer.WriteError($"RESD file with identifier {internalName.Value} is already loaded"); 50 return; 51 } 52 53 try 54 { 55 resdFiles[internalName.Value] = new LowLevelRESDParser(filePath.Value); 56 } 57 catch(Exception e) 58 { 59 writer.WriteError($"Could not load RESD file: {e}"); 60 return; 61 } 62 writer.WriteLine($"RESD file from '{filePath.Value}' loaded under identifier '{internalName.Value}'"); 63 } 64 65 [Runnable] Run(ICommandInteraction writer, [Values(R, R)] LiteralToken action, LiteralToken internalName)66 public void Run(ICommandInteraction writer, [Values("unload", "list-blocks")] LiteralToken action, LiteralToken internalName) 67 { 68 if(!TryGetResdFile(writer, internalName.Value, out _)) 69 { 70 return; 71 } 72 73 switch(action.Value) 74 { 75 case "unload": 76 Unload(writer, internalName.Value); 77 break; 78 case "list-blocks": 79 ListBlocks(writer, internalName.Value); 80 break; 81 } 82 } 83 84 [Runnable] Run(ICommandInteraction writer, [Values(R)] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, LiteralToken property)85 public void Run(ICommandInteraction writer, [Values("get-prop")] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, LiteralToken property) 86 { 87 RESDProperty enumValue; 88 if(!Enum.TryParse(property.Value, false, out enumValue)) 89 { 90 writer.WriteError($"{property.Value} is not a valid property."); 91 writer.WriteError($"Valid properties are: {RESDPropertyNames}"); 92 return; 93 } 94 DoForBlockWithIndex(writer, internalName.Value, index.Value, (block) => 95 { 96 switch(enumValue) 97 { 98 case RESDProperty.SampleType: 99 writer.WriteLine($"{block.SampleType}"); 100 break; 101 case RESDProperty.ChannelID: 102 writer.WriteLine($"{block.ChannelId}"); 103 break; 104 case RESDProperty.StartTime: 105 writer.WriteLine($"{TimeStampToTimeInterval(block.StartTime)}"); 106 break; 107 case RESDProperty.EndTime: 108 writer.WriteLine($"{TimeStampToTimeInterval(block.GetEndTime())}"); 109 break; 110 case RESDProperty.Duration: 111 writer.WriteLine($"{TimeStampToTimeInterval(block.Duration)}"); 112 break; 113 default: 114 throw new Exception("unreachable"); 115 } 116 }); 117 } 118 119 [Runnable] Run(ICommandInteraction writer, [Values(R)] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, StringToken startTimeString, DecimalIntegerToken count)120 public void Run(ICommandInteraction writer, [Values("get-samples")] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, StringToken startTimeString, DecimalIntegerToken count) 121 { 122 if(!TimeInterval.TryParse(startTimeString.Value, out var startTime)) 123 { 124 writer.WriteError($"{startTimeString.Value} is invalid time-interval"); 125 return; 126 } 127 128 DoForBlockWithIndex(writer, internalName.Value, index.Value, (block) => 129 { 130 foreach(var kv in block.Samples.SkipWhile(kv => kv.Key < startTime).Take((int)count.Value)) 131 { 132 writer.WriteLine($"{kv.Key}: {kv.Value}"); 133 } 134 }); 135 } 136 137 [Runnable] Run(ICommandInteraction writer, [Values(R)] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, StringToken range)138 public void Run(ICommandInteraction writer, [Values("get-samples-range")] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, StringToken range) 139 { 140 var delimiterIndex = range.Value.IndexOf(".."); 141 if(delimiterIndex == -1) 142 { 143 writer.WriteError($"{range.Value} is invalid range"); 144 return; 145 } 146 147 var startTimeString = range.Value.Substring(0, delimiterIndex); 148 var durationString = range.Value.Substring(delimiterIndex + 2, range.Value.Length - delimiterIndex - 2); 149 if(String.IsNullOrEmpty(startTimeString) || 150 String.IsNullOrEmpty(durationString) || 151 !TimeInterval.TryParse(startTimeString, out var startTime) || 152 !TimeInterval.TryParse(durationString, out var duration)) 153 { 154 writer.WriteError($"{range.Value} is invalid range"); 155 return; 156 } 157 158 DoForBlockWithIndex(writer, internalName.Value, index.Value, (block) => 159 { 160 foreach(var kv in block.Samples.SkipWhile(kv => kv.Key < startTime).TakeWhile(kv => kv.Key <= startTime + duration)) 161 { 162 writer.WriteLine($"{kv.Key}: {kv.Value}"); 163 } 164 }); 165 } 166 167 [Runnable] Run(ICommandInteraction writer, [Values(R)] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, StringToken startTimeString, StringToken endTimeString)168 public void Run(ICommandInteraction writer, [Values("get-samples-range")] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index, StringToken startTimeString, StringToken endTimeString) 169 { 170 if(!TimeInterval.TryParse(startTimeString.Value, out var startTime)) 171 { 172 writer.WriteError($"{startTimeString.Value} is invalid time-interval"); 173 return; 174 } 175 if(!TimeInterval.TryParse(endTimeString.Value, out var endTime)) 176 { 177 writer.WriteError($"{endTimeString.Value} is invalid time-interval"); 178 return; 179 } 180 181 DoForBlockWithIndex(writer, internalName.Value, index.Value, (block) => 182 { 183 foreach(var kv in block.Samples.SkipWhile(kv => kv.Key < startTime).TakeWhile(kv => kv.Key <= endTime)) 184 { 185 writer.WriteLine($"{kv.Key}: {kv.Value}"); 186 } 187 }); 188 } 189 190 [Runnable] Run(ICommandInteraction writer, [Values(R)] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index)191 public void Run(ICommandInteraction writer, [Values("describe-block")] LiteralToken action, LiteralToken internalName, DecimalIntegerToken index) 192 { 193 DoForBlockWithIndex(writer, internalName.Value, index.Value, (block) => 194 { 195 writer.WriteLine($"Index: {index.Value}"); 196 writer.WriteLine($"Sample type: {block.SampleType}"); 197 writer.WriteLine($"Channel ID: {block.ChannelId}"); 198 writer.WriteLine($"Start Time: {TimeStampToTimeInterval(block.StartTime)}"); 199 writer.WriteLine($"End Time: {TimeStampToTimeInterval(block.GetEndTime())}"); 200 writer.WriteLine($"Duration: {TimeStampToTimeInterval(block.Duration)}"); 201 writer.WriteLine($"Samples count: {block.SamplesCount}"); 202 203 foreach(var kv in block.ExtraInformation) 204 { 205 writer.WriteLine($"{kv.Key}: {kv.Value}"); 206 } 207 208 if(block.Metadata.Count == 0) 209 { 210 return; 211 } 212 213 writer.WriteLine($"Metadata: "); 214 foreach(var item in block.Metadata) 215 { 216 writer.WriteLine($"\t{item.Key} = {item.Value}"); 217 } 218 }); 219 } 220 221 public enum RESDProperty 222 { 223 SampleType, 224 ChannelID, 225 StartTime, 226 EndTime, 227 Duration, 228 SamplesCount, 229 } 230 ResdCommand(Monitor monitor)231 public ResdCommand(Monitor monitor) 232 : base(monitor, "resd", "introspection for RESD files") 233 { 234 resdFiles = new Dictionary<string, LowLevelRESDParser>(); 235 } 236 Unload(ICommandInteraction writer, string internalName)237 private void Unload(ICommandInteraction writer, string internalName) 238 { 239 resdFiles.Remove(internalName); 240 } 241 ListBlocks(ICommandInteraction writer, string internalName)242 private void ListBlocks(ICommandInteraction writer, string internalName) 243 { 244 writer.WriteLine($"Blocks in {internalName}:"); 245 foreach(var item in resdFiles[internalName].GetDataBlockEnumerator().Select((block, idx) => new { idx, block })) 246 { 247 writer.WriteLine($"{item.idx + 1}. [{TimeStampToTimeInterval(item.block.StartTime)}..{TimeStampToTimeInterval(item.block.GetEndTime())}] {item.block.SampleType}:{item.block.ChannelId}"); 248 } 249 } 250 TryGetResdFile(ICommandInteraction writer, string internalName, out LowLevelRESDParser resdFile)251 private bool TryGetResdFile(ICommandInteraction writer, string internalName, out LowLevelRESDParser resdFile) 252 { 253 if(!resdFiles.TryGetValue(internalName, out resdFile)) 254 { 255 writer.WriteError($"RESD file with identifier {internalName} doesn't exist"); 256 return false; 257 } 258 return true; 259 } 260 DoForBlockWithIndex(ICommandInteraction writer, string internalName, long index, Action<IDataBlock> callback)261 private void DoForBlockWithIndex(ICommandInteraction writer, string internalName, long index, Action<IDataBlock> callback) 262 { 263 if(!TryGetResdFile(writer, internalName, out var resdFile)) 264 { 265 return; 266 } 267 268 if(index <= 0) 269 { 270 writer.WriteError($"Index should be greater than 0"); 271 return; 272 } 273 274 var i = 0; 275 foreach(var block in resdFile.GetDataBlockEnumerator()) 276 { 277 if(++i == index) 278 { 279 callback(block); 280 return; 281 } 282 } 283 284 writer.WriteError($"Block with index {index} doesn't exist"); 285 } 286 TimeStampToTimeInterval(ulong timeStamp)287 private TimeInterval TimeStampToTimeInterval(ulong timeStamp) 288 { 289 return TimeInterval.FromMicroseconds(timeStamp / 1000); 290 } 291 292 private string RESDPropertyNames => String.Join(", ", Enum.GetNames(typeof(RESDProperty))); 293 294 private readonly IDictionary<string, LowLevelRESDParser> resdFiles; 295 } 296 } 297 298