public void SetData(ref ProfilerTrace trace)
        {
            m_Trace = trace;

            m_ThreadSelection.menu.MenuItems().Clear();
            m_ThreadSelection.SetEnabled(true);
            m_CurrentThread = -1;

            for (int i = 0; i < m_Trace.Threads.Length; i++)
            {
                var    thread = m_Trace.Threads[i];
                string threadName;
                if (thread.ThreadName.UTF8LengthInBytes == 0)
                {
                    threadName = "Thread " + i + " (unnamed)";
                }
                else
                {
                    threadName = thread.ThreadName.ToString();
                }
                int index = i;
                m_ThreadSelection.menu.AppendAction(threadName, action =>
                {
                    m_ThreadSelection.text = action.name;
                    m_CurrentThread        = index;
                });
            }

            m_ThreadSelection.text = "Select thread";
        }
Esempio n. 2
0
        public static unsafe void ReadProfilerTrace(ref ProfilerTrace trace, Stream stream, Allocator allocator)
        {
            var basePos = stream.Position;
            var reader  = new RawReader(stream);
            ProfilerDataSerializationHeader serializationHeader = default;

            reader.Read(&serializationHeader);
            Debug.Assert(basePos + serializationHeader.TotalLength <= stream.Length);

            ProfilerTraceHeader traceHeader = default;

            reader.Read(&traceHeader);
            trace.Header = traceHeader;

            Read(out trace.Samples, serializationHeader.NumSamples, basePos + serializationHeader.SamplesOffset);
            Read(out trace.StackFrames, serializationHeader.NumStackFrames, basePos + serializationHeader.StackFramesOffset);
            Read(out trace.Functions, serializationHeader.NumFunctions, basePos + serializationHeader.FunctionsOffset);
            Read(out trace.Modules, serializationHeader.NumModules, basePos + serializationHeader.ModulesOffset);
            Read(out trace.Threads, serializationHeader.NumThreads, basePos + serializationHeader.ThreadsOffset);
            Read(out trace.BlobData, serializationHeader.BlobLength, basePos + serializationHeader.BlobOffset);

            void Read <T>(out NativeArray <T> arr, int num, long offset) where T : unmanaged
            {
                stream.Position = basePos + offset;
                arr             = new NativeArray <T>(num, allocator, NativeArrayOptions.UninitializedMemory);
                reader.ReadBytes(arr.GetUnsafePtr(), sizeof(T) * num);
            }
        }
 public void ClearData()
 {
     if (m_HasData)
     {
         m_HasData = false;
         m_HeatMap.Dispose();
         m_Trace = default;
         m_ExportCsvButton.SetEnabled(false);
         m_ThreadSelection.SetEnabled(false);
         m_HeatMapTreeView.Reload();
     }
 }
 public void ClearData()
 {
     m_Trace             = default;
     m_CurrentThread     = -1;
     m_CurrentSearchTerm = null;
     m_MergedStackFrames.TryDispose();
     m_MergedSamples.TryDispose();
     m_FilteredSamples.TryDispose();
     m_FunctionWhiteList.TryDispose();
     m_FunctionSamples.TryDispose();
     m_SubtreeTreeView.ClearData();
     m_SubtreeTreeView.Reload();
 }
Esempio n. 5
0
        public static unsafe void WriteProfilerTrace(ref ProfilerTrace trace, Stream stream)
        {
            var writer = new RawWriter(stream);
            var header = new ProfilerDataSerializationHeader
            {
                Version        = ProfilerTraceVersion,
                NumFunctions   = trace.Functions.Length,
                NumModules     = trace.Modules.Length,
                NumSamples     = trace.Samples.Length,
                NumStackFrames = trace.StackFrames.Length,
                NumThreads     = trace.Threads.Length,
                BlobLength     = trace.BlobData.Length
            };
            long headerPos = stream.Position;

            // we'll write the header again later
            writer.Write(&header);

            var profTrace = trace;

            writer.Write(&profTrace.Header);

            header.SamplesOffset = stream.Position - headerPos;
            writer.WriteArray(profTrace.Samples);

            header.StackFramesOffset = stream.Position - headerPos;
            writer.WriteArray(profTrace.StackFrames);

            header.FunctionsOffset = stream.Position - headerPos;
            writer.WriteArray(profTrace.Functions);

            header.ModulesOffset = stream.Position - headerPos;
            writer.WriteArray(profTrace.Modules);

            header.ThreadsOffset = stream.Position - headerPos;
            writer.WriteArray(profTrace.Threads);

            header.BlobOffset = stream.Position - headerPos;
            writer.WriteArray(profTrace.BlobData);

            stream.Flush();
            header.TotalLength = (int)(stream.Position - headerPos);
            stream.Position    = headerPos;
            writer.Write(&header);

            stream.Flush();
        }
Esempio n. 6
0
 public void ClearData()
 {
     m_Trace = default;
     for (int i = 0; i < m_TreeDataPerThread.Count; i++)
     {
         m_TreeDataPerThread[i].Dispose();
     }
     m_TreeDataPerThread.Clear();
     if (m_MergedStackFrames.IsCreated)
     {
         m_MergedStackFrames.Dispose();
     }
     if (m_MergedSamples.IsCreated)
     {
         m_MergedSamples.Dispose();
     }
     m_TreeView.ClearData();
     m_TreeView.CollapseAll();
     m_TreeView.Reload();
 }
Esempio n. 7
0
        static unsafe void ReadEtlFile(string etlPath, IEnumerable <string> pdbWhitelist, out ProfilerTrace profTrace, Allocator allocator)
        {
            var monoFunctions     = new Dictionary <IntPtr, MonoJitInfo>();
            var discoveredModules = DiscoveredData <DiscoveredModule> .Make(DiscoveredModule.Invalid);

            var discoveredStackFrames = DiscoveredData <CallStackIndex> .Make(CallStackIndex.Invalid);

            var discoveredFunctions = DiscoveredData <DiscoveredFunction> .Make(DiscoveredFunction.Invalid);

            var discoveredThreads = DiscoveredData <ThreadIndex> .Make(ThreadIndex.Invalid);

            string additionalSymbolPath = BurstPath;

#if UNITY_EDITOR
            additionalSymbolPath += ";" + Path.GetDirectoryName(EditorApplication.applicationPath);
#endif

            var options = new TraceLogOptions()
            {
                AlwaysResolveSymbols = true,
                LocalSymbolsOnly     = false,
                AllowUnsafeSymbols   = true,
                AdditionalSymbolPath = additionalSymbolPath,
                ShouldResolveSymbols = path =>
                {
                    path = path.ToLowerInvariant();
                    return(pdbWhitelist.Any(x => path.EndsWith(x)));
                }
            };

            profTrace = new ProfilerTrace();
            using (var trace = TraceLog.OpenOrConvert(etlPath, options))
            {
                var processIndex = FindUnityProcessIndex(trace.Processes);
                var processId    = trace.Processes[processIndex].ProcessID;

                profTrace.Header = new ProfilerTraceHeader
                {
                    SamplingInterval = trace.SampleProfileInterval.TotalMilliseconds,
                    SessionStart     = trace.SessionStartTime.Ticks,
                    SessionEnd       = trace.SessionEndTime.Ticks,
                };

                var sampleRange = new Dictionary <MethodIndex, ulong>();
                using (var samples = new NativeList <SampleData>(Allocator.Temp))
                {
                    SampleData sampleData = default;
                    foreach (var evt in trace.Events)
                    {
                        var sample = evt as SampledProfileTraceData;
                        if (sample == null || sample.ProcessID != processId || sample.NonProcess)
                        {
                            continue;
                        }
                        // TODO sample has a count field that we are currently ignoring. should we?
                        sampleData.Address    = (long)sample.InstructionPointer;
                        sampleData.ThreadIdx  = discoveredThreads.AddData(sample.Thread()?.ThreadIndex ?? ThreadIndex.Invalid);
                        sampleData.TimeStamp  = sample.TimeStampRelativeMSec;
                        sampleData.StackTrace = discoveredStackFrames.AddData(sample.CallStackIndex());
                        var codeAddress = sample.IntructionPointerCodeAddress();
                        sampleData.Function = GetFunctionIndex(codeAddress);
                        samples.Add(sampleData);

                        if (sampleData.Function >= 0)
                        {
                            var index = discoveredFunctions.Data[sampleData.Function].Index;
                            if (index == MethodIndex.Invalid)
                            {
                                continue;
                            }
                            if (!sampleRange.TryGetValue(index, out ulong previousMax))
                            {
                                sampleRange[index] = sample.InstructionPointer;
                            }
                            else
                            {
                                sampleRange[index] = sample.InstructionPointer > previousMax
                                    ? sample.InstructionPointer
                                    : previousMax;
                            }
                        }
                    }
                    profTrace.Samples = samples.ToArray(allocator);
                }

                using (var stackFrames = new NativeList <StackFrameData>(Allocator.Temp))
                {
                    StackFrameData stackTraceData = default;
                    // N.B. this loop adds more stack frames as it executes
                    for (int idx = 0; idx < discoveredStackFrames.Count; idx++)
                    {
                        var stack = trace.CallStacks[discoveredStackFrames.Data[idx]];
                        stackTraceData.Address          = (long)stack.CodeAddress.Address;
                        stackTraceData.Depth            = stack.Depth;
                        stackTraceData.CallerStackFrame = discoveredStackFrames.AddData(stack.Caller?.CallStackIndex ?? CallStackIndex.Invalid);
                        stackTraceData.Function         = GetFunctionIndex(stack.CodeAddress);
                        stackFrames.Add(stackTraceData);
                    }
                    profTrace.StackFrames = stackFrames.ToArray(allocator);
                }

                using (var functions = new NativeList <FunctionData>(Allocator.Temp))
                {
                    var burstModules = new Dictionary <int, bool>
                    {
                        { -1, false }
                    };
                    var          blobData = new NativeList <byte>(Allocator.Temp);
                    FunctionData funcData = default;
                    foreach (var func in discoveredFunctions.Data)
                    {
                        bool copyCode;
                        if (func.Index != MethodIndex.Invalid)
                        {
                            var method = trace.CodeAddresses.Methods[func.Index];
                            if (method.MethodRva > 0 && method.MethodModuleFile != null)
                            {
                                funcData.BaseAddress = method.MethodModuleFile.ImageBase + (ulong)method.MethodRva;
                            }
                            else
                            {
                                funcData.BaseAddress = 0;
                            }
                            if (funcData.BaseAddress > 0 && method.MethodIndex != MethodIndex.Invalid && sampleRange.TryGetValue(method.MethodIndex, out var maxAddress))
                            {
                                Debug.Assert(maxAddress >= funcData.BaseAddress);
                                // use the value of the furthest sample to estimate the length of the function
                                funcData.Length = 16 + (int)(maxAddress - funcData.BaseAddress);
                            }
                            else
                            {
                                funcData.Length = 0;
                            }
                            funcData.Module = discoveredModules.AddData(DiscoveredModule.FromIndex(method.MethodModuleFileIndex));
                            funcData.Name.CopyFrom(method.FullMethodName);
                            if (!burstModules.TryGetValue((int)method.MethodModuleFileIndex, out var isBurst))
                            {
                                isBurst = method.MethodModuleFile.FilePath.ToLowerInvariant().Contains("burst");
                                burstModules[(int)method.MethodModuleFileIndex] = isBurst;
                            }
                            copyCode = isBurst && funcData.Length > 0;
                        }
                        else
                        {
                            var jitData = func.MonoMethod;
                            funcData.BaseAddress = (ulong)jitData.CodeStart.ToInt64();
                            funcData.Length      = jitData.CodeSize;
                            funcData.Module      = discoveredModules.AddData(DiscoveredModule.FromMonoModule(jitData.Method.Module));

                            var fullName = MonoFunctionName(jitData.Method);
                            funcData.Name.CopyFrom(fullName);
                            copyCode = true;
                        }

                        if (copyCode)
                        {
                            funcData.CodeBlobOffset = blobData.Length;
                            blobData.AddRange((void *)funcData.BaseAddress, funcData.Length);
                        }
                        else
                        {
                            funcData.CodeBlobOffset = -1;
                        }

                        functions.Add(funcData);
                    }
                    profTrace.Functions = functions.ToArray(allocator);
                    profTrace.BlobData  = blobData.ToArray(allocator);
                }

                using (var modules = new NativeList <ModuleData>(Allocator.Temp))
                {
                    // make sure that all modules of the current process are included.
                    foreach (var module in trace.Processes[processIndex].LoadedModules)
                    {
                        discoveredModules.AddData(new DiscoveredModule {
                            Index = module.ModuleFile.ModuleFileIndex
                        });
                    }

                    ModuleData moduleData = default;
                    foreach (var dm in discoveredModules.Data)
                    {
                        if (dm.MonoModule != null)
                        {
                            moduleData          = default;
                            moduleData.IsMono   = true;
                            moduleData.FilePath = dm.MonoModule.Assembly.Location;
                        }
                        else
                        {
                            var module = trace.ModuleFiles[dm.Index];
                            moduleData.IsMono    = false;
                            moduleData.Checksum  = module.ImageChecksum;
                            moduleData.ImageBase = module.ImageBase;
                            moduleData.ImageEnd  = module.ImageEnd;
                            moduleData.PdbAge    = module.PdbAge;
                            moduleData.FilePath.CopyFrom(module.FilePath);
                            moduleData.PdbName.CopyFrom(module.PdbName);
                            var guidBytes = module.PdbSignature.ToByteArray();

                            fixed(byte *ptr = guidBytes)
                            UnsafeUtility.MemCpy(moduleData.PdbGuid, ptr, 16);
                        }
                        modules.Add(moduleData);
                    }
                    profTrace.Modules = modules.ToArray(allocator);
                }

                using (var threads = new NativeList <ThreadData>(Allocator.Temp))
                {
                    ThreadData threadData = default;
                    foreach (var t in discoveredThreads.Data)
                    {
                        var thread = trace.Threads[t];
                        threadData.ThreadName.CopyFrom(thread.ThreadInfo ?? "");
                        threads.Add(threadData);
                    }
                    profTrace.Threads = threads.ToArray(allocator);
                }
            }

            int GetFunctionIndex(TraceCodeAddress address)
            {
                var method = address.Method;

                if (method == null)
                {
                    var jit = Mono.GetJitInfoAnyDomain(new IntPtr((long)address.Address), out _);
                    if (jit.Method == null)
                    {
                        return(-1);
                    }
                    if (!monoFunctions.TryGetValue(jit.CodeStart, out var actualJit))
                    {
                        monoFunctions.Add(jit.CodeStart, jit);
                        actualJit = jit;
                    }
                    return(discoveredFunctions.AddData(DiscoveredFunction.FromMethod(actualJit)));
                }
                return(discoveredFunctions.AddData(new DiscoveredFunction
                {
                    Index = method.MethodIndex
                }));
            }

            string MonoFunctionName(MethodBase method)
            {
                if (method == null)
                {
                    return("???");
                }
                if (method.DeclaringType.DeclaringType == null)
                {
                    return(method.DeclaringType.Namespace + '.' + method.DeclaringType.Name + '.' + method.Name);
                }
                var outerMost = method.DeclaringType.DeclaringType;
                var outer     = method.DeclaringType;

                return(outerMost.Namespace + '.' + outerMost.Name + '+' + outer.Name + '.' + method.Name);
            }
        }