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