Skip to content

Commit

Permalink
Add basic code sampler for macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Dec 2, 2024
1 parent b290c6f commit 829faa8
Show file tree
Hide file tree
Showing 9 changed files with 537 additions and 1 deletion.
15 changes: 15 additions & 0 deletions src/Ultra.Sampler/Ultra.Sampler.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="Ultra.Tests"/>
</ItemGroup>

</Project>
154 changes: 154 additions & 0 deletions src/Ultra.Sampler/UltraSampler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Ultra.Sampler;

using static libSystem;

public static class UltraSampler
{
private static readonly object Lock = new();
private static Thread? _thread;
private static bool _stopped;
private const int MaximumFrames = 65536;

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)], EntryPoint = "ultra_sampler_start")]
internal static void NativeStart() => Start();

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)], EntryPoint = "ultra_sampler_stop")]
internal static void NativeStop() => Stop();

public static void Start()
{
lock (Lock)
{
if (_thread is not null) return;

_thread = new Thread(RunImpl)
{
IsBackground = true,
Name = "Ultra-Sampler",
Priority = ThreadPriority.Highest
};
_thread.Start();
}
}

public static void Stop()
{
lock (Lock)
{
if (_thread is null) return;

_stopped = true;
_thread.Join();
_thread = null;
_stopped = false;
}
}

private static void RunImpl()
{
try
{
task_for_pid(mach_task_self(), Process.GetCurrentProcess().Id, out var rootTask)
.ThrowIfError("task_for_pid");

pthread_threadid_np(0, out var currentThreadId)
.ThrowIfError("pthread_threadid_np");

var frames = GC.AllocateUninitializedArray<ulong>(MaximumFrames, pinned: true);

while (!_stopped)
{
if (UltraSamplerSource.Log.IsEnabled())
{
Sample(rootTask, currentThreadId, frames);
}

// Sleep for 1ms
Thread.Sleep(1);
}
}
catch (Exception ex)
{
Trace.TraceError($"Ultra-Sampler unexpected exception while sampling: {ex}");
}
}

private static unsafe void Sample(mach_port_t rootTask, ulong currentThreadId, Span<ulong> frames)
{
mach_port_t* taskList;
task_threads(rootTask, &taskList, out uint taskCount)
.ThrowIfError("task_threads");

thread_identifier_info threadInfo = new();

ulong* pFrames = (ulong*)Unsafe.AsPointer(ref frames[0]);

for (var i = 0; i < taskCount; i++)
{
var threadPort = taskList[i];
try
{
int threadInfoCount = THREAD_IDENTIFIER_INFO_COUNT;
thread_info(threadPort, THREAD_IDENTIFIER_INFO, out threadInfo, ref threadInfoCount)
.ThrowIfError("thread_info");

//var thread_t = pthread_from_mach_thread_np(threadPort);

//if (thread_t != 0)
//{
// pthread_getname_np(thread_t, nameBuffer, 256)
// .ThrowIfError("pthread_getname_np");
// Console.WriteLine($"Thread ID: {threadInfo.thread_id} Name: {Marshal.PtrToStringAnsi((IntPtr)nameBuffer)}");
//}
//else
//{
// Console.WriteLine($"Thread ID: {threadInfo.thread_id}");
//}

if (threadInfo.thread_id == currentThreadId) continue;

thread_suspend(taskList[i])
.ThrowIfError("thread_suspend");

try
{
arm_thread_state64_t armThreadState = new arm_thread_state64_t();
int armThreadStateCount = ARM_THREAD_STATE64_COUNT;

thread_get_state(threadPort, ARM_THREAD_STATE64, (nint)(void*)&armThreadState, ref armThreadStateCount)
.ThrowIfError("thread_get_state");

//Console.WriteLine($"sp: 0x{armThreadState.__sp:X8}, fp: 0x{armThreadState.__fp:X8}, lr: 0x{armThreadState.__lr:X8}");
WalkCallStack(armThreadState.__sp, armThreadState.__fp, armThreadState.__lr, pFrames);
}
finally
{
thread_resume(threadPort)
.ThrowIfError("thread_resume");
}
}
catch (Exception ex)
{
Trace.TraceError($"Ultra-Sampler unexpected exception while sampling thread #{threadInfo.thread_id}: {ex}");
}
}
}

private static unsafe void WalkCallStack(ulong sp, ulong fp, ulong lr, ulong* frames)
{
int frameIndex = 0;
while (fp != 0 && frameIndex < MaximumFrames)
{
frames[frameIndex++] = lr;
//sp = fp + 16;
lr = *(ulong*)(fp + 8);
fp = *(ulong*)fp;
}

UltraSamplerSource.Log.Callstack(frames, frameIndex);
}
}
16 changes: 16 additions & 0 deletions src/Ultra.Sampler/UltraSamplerParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

namespace Ultra.Sampler;

public static class UltraSamplerParser
{
public const string Name = "Ultra-Sampler";

public const string IdAsString = "04E4DCBF-494F-4A77-B55E-F5C041A92F56";

public static readonly Guid Id = new(IdAsString);

public const int CallStackEvent = 1;
}
32 changes: 32 additions & 0 deletions src/Ultra.Sampler/UltraSamplerSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;

namespace Ultra.Sampler;

[EventSource(Name = UltraSamplerParser.Name, Guid = UltraSamplerParser.IdAsString)]
public class UltraSamplerSource : EventSource
{
public static readonly UltraSamplerSource Log = new();

private UltraSamplerSource()
{
}

[Event(UltraSamplerParser.CallStackEvent, Level = EventLevel.Verbose)]
[SkipLocalsInit]
[MethodImpl(MethodImplOptions.NoInlining)]
public unsafe void Callstack(ulong* pFrames, int count)
{
if (IsEnabled())
{
Unsafe.SkipInit(out EventSource.EventData evt);
evt.DataPointer = (nint)pFrames;
evt.Size = count * sizeof(ulong);
WriteEventCore(UltraSamplerParser.CallStackEvent, 1, &evt);
}
}
}
Loading

0 comments on commit 829faa8

Please sign in to comment.