1 // 2 // Copyright (c) 2010-2025 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.Collections.Generic; 9 using System.IO; 10 using System.Linq; 11 using System.Text; 12 using Antmicro.Renode.Logging; 13 14 namespace Antmicro.Renode.Peripherals.CPU.GuestProfiling 15 { 16 public class CollapsedStackProfiler : BaseProfiler 17 { CollapsedStackProfiler(TranslationCPU cpu, string filename, bool flushInstantly, long? fileSizeLimit = null, int? maximumNestedContexts = null)18 public CollapsedStackProfiler(TranslationCPU cpu, string filename, bool flushInstantly, long? fileSizeLimit = null, int? maximumNestedContexts = null) 19 : base(cpu, flushInstantly, maximumNestedContexts) 20 { 21 this.fileSizeLimit = fileSizeLimit; 22 stringBuffer = new StringBuilder(); 23 fileStream = new StreamWriter(filename); 24 } 25 Dispose()26 public override void Dispose() 27 { 28 base.Dispose(); 29 fileStream.Close(); 30 } 31 StackFrameAdd(ulong currentAddress, ulong returnAddress, ulong instructionsCount)32 public override void StackFrameAdd(ulong currentAddress, ulong returnAddress, ulong instructionsCount) 33 { 34 var currentSymbol = GetSymbolName(currentAddress); 35 cpu.Log(LogLevel.Debug, "Profiler: Pushing new frame with symbol: {0}; returnAddress: 0x{1:X}; currentAddress: 0x{2:X}", currentSymbol, returnAddress, currentAddress); 36 37 if(isFirstFrame) 38 { 39 isFirstFrame = false; 40 // We have to add the first frame, form which the first jump happened 41 // To infer this frame we use the current PC value, which should point to the jump instruction that triggered this event 42 var prevSymbol = GetSymbolName(cpu.PC.RawValue); 43 currentStack.Push(prevSymbol); 44 } 45 46 var instructionsElapsed = GetInstructionsDelta(instructionsCount); 47 AddStackToBufferWithDelta(instructionsElapsed); 48 49 currentStack.Push(currentSymbol); 50 51 CheckAndFlushBuffer(); 52 } 53 StackFramePop(ulong currentAddress, ulong returnAddress, ulong instructionsCount)54 public override void StackFramePop(ulong currentAddress, ulong returnAddress, ulong instructionsCount) 55 { 56 cpu.Log(LogLevel.Debug, "Profiler: Trying to pop frame with returnAddress: 0x{0:X} ({1}); currentAddress: 0x{2:X}", returnAddress, GetSymbolName(returnAddress), currentAddress); 57 if(currentStack.Count == 0) 58 { 59 cpu.Log(LogLevel.Error, "Profiler: Trying to return from frame while internal stack tracking is empty"); 60 return; 61 } 62 63 var instructionsElapsed = GetInstructionsDelta(instructionsCount); 64 AddStackToBufferWithDelta(instructionsElapsed); 65 66 currentStack.Pop(); 67 68 CheckAndFlushBuffer(); 69 } 70 OnContextChange(ulong newContextId)71 public override void OnContextChange(ulong newContextId) 72 { 73 if(newContextId == currentContextId) 74 { 75 return; 76 } 77 78 var instructionsElapsed = GetInstructionsDelta(cpu.ExecutedInstructions); 79 AddStackToBufferWithDelta(instructionsElapsed); 80 PushCurrentContextSafe(); 81 82 cpu.Log(LogLevel.Debug, "Profiler: Changing context from: 0x{0:X} to 0x{1:X}", currentContextId, newContextId); 83 84 if(!wholeExecution.ContainsKey(newContextId)) 85 { 86 wholeExecution.Add(newContextId, new ProfilerContext()); 87 } 88 89 currentContextId = newContextId; 90 currentContext.PopCurrentStack(); 91 92 CheckAndFlushBuffer(); 93 } 94 InterruptEnter(ulong interruptIndex)95 public override void InterruptEnter(ulong interruptIndex) 96 { 97 var instructionsElapsed = GetInstructionsDelta(cpu.ExecutedInstructions); 98 AddStackToBufferWithDelta(instructionsElapsed); 99 100 cpu.Log(LogLevel.Debug, "Profiler: Interrupt entry (pc 0x{0:X})- saving the stack", cpu.PC); 101 PushCurrentContextSafe(); 102 103 CheckAndFlushBuffer(); 104 } 105 InterruptExit(ulong interruptIndex)106 public override void InterruptExit(ulong interruptIndex) 107 { 108 var instructionsElapsed = GetInstructionsDelta(cpu.ExecutedInstructions); 109 AddStackToBufferWithDelta(instructionsElapsed); 110 111 cpu.Log(LogLevel.Debug, "Profiler: Interrupt exit - restoring the stack"); 112 currentContext.PopCurrentStack(); 113 114 CheckAndFlushBuffer(); 115 } 116 FlushBuffer()117 public override void FlushBuffer() 118 { 119 // DisableProfiler() calls Dispose() which calls FlushBuffer() 120 // This flag is required to prevent an infinite loop 121 if(isDisposing) 122 { 123 return; 124 } 125 126 lock(bufferLock) 127 { 128 if(fileSizeLimit.HasValue && (fileWrittenBytes + stringBuffer.Length) > fileSizeLimit) 129 { 130 isDisposing = true; 131 cpu.Log(LogLevel.Warning, "Profiler: Maximum file size exceeded, removing profiler"); 132 cpu.DisableProfiler(); 133 return; 134 } 135 136 fileStream.Write(stringBuffer.ToString()); 137 fileWrittenBytes += stringBuffer.Length; 138 stringBuffer.Clear(); 139 } 140 } 141 GetInstructionsDelta(ulong currentInstructionsCount)142 private ulong GetInstructionsDelta(ulong currentInstructionsCount) 143 { 144 currentInstructionsCount += cpu.SkipInstructions + cpu.SkippedInstructions; 145 ulong instructionsElapsed = checked(currentInstructionsCount - lastInstructionsCount); 146 lastInstructionsCount = currentInstructionsCount; 147 return instructionsElapsed; 148 } 149 AddStackToBufferWithDelta(ulong instructionDelta)150 private void AddStackToBufferWithDelta(ulong instructionDelta) 151 { 152 if(instructionDelta == 0) 153 { 154 // Speedscope doesn't draw segments with length of 0 155 // We can just skip them to save some file space 156 return; 157 } 158 159 var collapsedStack = FormatCollapsedStackString(currentStack); 160 AddToBuffer($"{collapsedStack} {instructionDelta}"); 161 } 162 AddToBuffer(string stringToAdd)163 private void AddToBuffer(string stringToAdd) 164 { 165 lock(bufferLock) 166 { 167 stringBuffer.AppendLine(stringToAdd); 168 } 169 } 170 CheckAndFlushBuffer()171 private void CheckAndFlushBuffer() 172 { 173 if(flushInstantly || stringBuffer.Length > BufferFlushLevel) 174 { 175 FlushBuffer(); 176 } 177 } 178 FormatCollapsedStackString(Stack<string> stack)179 private string FormatCollapsedStackString(Stack<string> stack) 180 { 181 return string.Join(";", stack.Reverse()); 182 } 183 GetCurrentStack()184 public override string GetCurrentStack() 185 { 186 var result = FormatCollapsedStackString(currentStack); 187 188 return result; 189 } 190 191 private readonly StringBuilder stringBuffer; 192 private readonly StreamWriter fileStream; 193 private readonly long? fileSizeLimit; 194 195 private ulong lastInstructionsCount; 196 private long fileWrittenBytes; 197 private bool isDisposing; 198 199 private const int BufferFlushLevel = 1000000; 200 } 201 } 202