Skip to content

Commit f6715e3

Browse files
committed
Add Thread Start/Stop and samplingId
1 parent 56a2808 commit f6715e3

8 files changed

+254
-30
lines changed

src/Ultra.Core/Parser/UltraNativeCallstackTraceEvent.cs

+19-12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal sealed class UltraNativeCallstackTraceEvent : TraceEvent
1111
{
1212
private static readonly string[] _payloadNames =
1313
[
14+
nameof(SamplingId),
1415
nameof(FrameThreadId),
1516
nameof(ThreadState),
1617
nameof(ThreadCpuUsage),
@@ -26,17 +27,21 @@ internal UltraNativeCallstackTraceEvent(Action<UltraNativeCallstackTraceEvent>?
2627
_target = target;
2728
}
2829

29-
public ulong FrameThreadId => (ulong)GetInt64At(0);
30+
public ulong SamplingId => (ulong)GetInt64At(0);
3031

31-
public UltraSamplerThreadState ThreadState => (UltraSamplerThreadState)GetInt32At(8);
32+
public ulong FrameThreadId => (ulong)GetInt64At(8);
3233

33-
public double ThreadCpuUsage => GetInt32At(12) / 1000.0;
34+
public UltraSamplerThreadState ThreadState => (UltraSamplerThreadState)GetInt32At(16);
3435

35-
public int PreviousFrameCount => GetInt32At(16);
36+
public int ThreadCpuUsageAsInt => GetInt32At(20);
37+
38+
public double ThreadCpuUsage => ThreadCpuUsageAsInt / 1000.0;
39+
40+
public int PreviousFrameCount => GetInt32At(24);
3641

37-
public int FrameSize => GetInt32At(20);
42+
public int FrameSize => GetInt32At(28);
3843

39-
public unsafe ReadOnlySpan<ulong> FrameAddresses => new((byte*)DataStart + 24, FrameSize / sizeof(ulong));
44+
public unsafe ReadOnlySpan<ulong> FrameAddresses => new((byte*)DataStart + 32, FrameSize / sizeof(ulong));
4045

4146
/// <inheritdoc />
4247

@@ -45,16 +50,18 @@ public override object PayloadValue(int index)
4550
switch (index)
4651
{
4752
case 0:
48-
return FrameThreadId;
53+
return SamplingId;
4954
case 1:
50-
return (int)ThreadState;
55+
return FrameThreadId;
5156
case 2:
52-
return GetInt32At(12);
57+
return (int)ThreadState;
5358
case 3:
54-
return PreviousFrameCount;
59+
return ThreadCpuUsageAsInt;
5560
case 4:
56-
return FrameSize;
61+
return PreviousFrameCount;
5762
case 5:
63+
return FrameSize;
64+
case 6:
5865
return FrameAddresses.ToArray();
5966
default:
6067
throw new ArgumentOutOfRangeException(nameof(index));
@@ -80,4 +87,4 @@ protected override void Dispatch()
8087
protected override void Validate()
8188
{
8289
}
83-
}
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
2+
// Licensed under the BSD-Clause 2 license.
3+
// See license.txt file in the project root for full license information.
4+
5+
using System.Text;
6+
using Microsoft.Diagnostics.Tracing;
7+
using Ultra.Sampler;
8+
9+
namespace Ultra.Core;
10+
11+
internal sealed class UltraNativeThreadStartTraceEvent : TraceEvent
12+
{
13+
private static readonly string[] _payloadNames =
14+
[
15+
nameof(SamplingId),
16+
nameof(FrameThreadId),
17+
nameof(ThreadNameSize),
18+
nameof(ThreadName),
19+
];
20+
21+
private Action<UltraNativeThreadStartTraceEvent>? _target;
22+
23+
internal UltraNativeThreadStartTraceEvent(Action<UltraNativeThreadStartTraceEvent>? target, int eventID, int task, string taskName, Guid taskGuid, int opcode, string opcodeName, Guid providerGuid, string providerName) : base(eventID, task, taskName, taskGuid, opcode, opcodeName, providerGuid, providerName)
24+
{
25+
_target = target;
26+
}
27+
28+
public ulong SamplingId => (ulong)GetInt64At(0);
29+
30+
public ulong FrameThreadId => (ulong)GetInt64At(8);
31+
32+
public int ThreadNameSize => GetInt32At(16);
33+
34+
public unsafe byte* ThreadNamePointer => (byte*)DataStart + 24 + ThreadNameSize;
35+
36+
public unsafe string ThreadName => Encoding.UTF8.GetString(new ReadOnlySpan<byte>(ThreadNamePointer, ThreadNameSize));
37+
38+
/// <inheritdoc />
39+
40+
public override object PayloadValue(int index)
41+
{
42+
switch (index)
43+
{
44+
case 0:
45+
return SamplingId;
46+
case 1:
47+
return FrameThreadId;
48+
case 2:
49+
return ThreadNameSize;
50+
case 3:
51+
unsafe
52+
{
53+
return new ReadOnlySpan<byte>(ThreadNamePointer, ThreadNameSize).ToArray();
54+
}
55+
default:
56+
throw new ArgumentOutOfRangeException(nameof(index));
57+
}
58+
}
59+
60+
public override string[] PayloadNames => _payloadNames;
61+
62+
/// <inheritdoc />
63+
protected override Delegate? Target
64+
{
65+
get => _target;
66+
set => _target = (Action<UltraNativeThreadStartTraceEvent>?)value;
67+
}
68+
69+
/// <inheritdoc />
70+
protected override void Dispatch()
71+
{
72+
_target?.Invoke(this);
73+
}
74+
75+
/// <inheritdoc />
76+
protected override void Validate()
77+
{
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
2+
// Licensed under the BSD-Clause 2 license.
3+
// See license.txt file in the project root for full license information.
4+
5+
using Microsoft.Diagnostics.Tracing;
6+
7+
namespace Ultra.Core;
8+
9+
internal sealed class UltraNativeThreadStopTraceEvent : TraceEvent
10+
{
11+
private static readonly string[] _payloadNames =
12+
[
13+
nameof(SamplingId),
14+
nameof(FrameThreadId),
15+
];
16+
17+
private Action<UltraNativeThreadStopTraceEvent>? _target;
18+
19+
internal UltraNativeThreadStopTraceEvent(Action<UltraNativeThreadStopTraceEvent>? target, int eventID, int task, string taskName, Guid taskGuid, int opcode, string opcodeName, Guid providerGuid, string providerName) : base(eventID, task, taskName, taskGuid, opcode, opcodeName, providerGuid, providerName)
20+
{
21+
_target = target;
22+
}
23+
24+
public ulong SamplingId => (ulong)GetInt64At(0);
25+
26+
public ulong FrameThreadId => (ulong)GetInt64At(8);
27+
28+
/// <inheritdoc />
29+
30+
public override object PayloadValue(int index)
31+
{
32+
switch (index)
33+
{
34+
case 0:
35+
return SamplingId;
36+
case 1:
37+
return FrameThreadId;
38+
default:
39+
throw new ArgumentOutOfRangeException(nameof(index));
40+
}
41+
}
42+
43+
public override string[] PayloadNames => _payloadNames;
44+
45+
/// <inheritdoc />
46+
protected override Delegate? Target
47+
{
48+
get => _target;
49+
set => _target = (Action<UltraNativeThreadStopTraceEvent>?)value;
50+
}
51+
52+
/// <inheritdoc />
53+
protected override void Dispatch()
54+
{
55+
_target?.Invoke(this);
56+
}
57+
58+
/// <inheritdoc />
59+
protected override void Validate()
60+
{
61+
}
62+
}

src/Ultra.Core/Parser/UltraSamplerParser.cs

+23-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ public event Action<UltraNativeModuleTraceEvent> EventNativeModule
3131
add => source.RegisterEventTemplate(CreateUltraNativeModuleTraceEvent(value));
3232
remove => source.UnregisterEventTemplate(value, UltraSamplerConstants.NativeModuleEventId, ProviderGuid);
3333
}
34-
34+
35+
public event Action<UltraNativeThreadStartTraceEvent> EventNativeThreadStart
36+
{
37+
add => source.RegisterEventTemplate(CreateUltraNativeThreadStartTraceEvent(value));
38+
remove => source.UnregisterEventTemplate(value, UltraSamplerConstants.NativeThreadStartEventId, ProviderGuid);
39+
}
40+
41+
public event Action<UltraNativeThreadStopTraceEvent> EventNativeThreadStop
42+
{
43+
add => source.RegisterEventTemplate(CreateUltraNativeThreadStopTraceEvent(value));
44+
remove => source.UnregisterEventTemplate(value, UltraSamplerConstants.NativeThreadStopEventId, ProviderGuid);
45+
}
46+
3547
/// <inheritdoc />
3648
protected override string GetProviderName() => UltraSamplerConstants.ProviderName;
3749

@@ -44,6 +56,8 @@ protected override void EnumerateTemplates(Func<string, string, EventFilterRespo
4456
[
4557
CreateUltraNativeCallstackTraceEvent(null),
4658
CreateUltraNativeModuleTraceEvent(null),
59+
CreateUltraNativeThreadStartTraceEvent(null),
60+
CreateUltraNativeThreadStopTraceEvent(null)
4761
];
4862
}
4963

@@ -57,8 +71,14 @@ protected override void EnumerateTemplates(Func<string, string, EventFilterRespo
5771
}
5872

5973
private static TraceEvent CreateUltraNativeCallstackTraceEvent(Action<UltraNativeCallstackTraceEvent>? value)
60-
=> new UltraNativeCallstackTraceEvent(value, UltraSamplerConstants.NativeCallStackEventId, 0, "OnNativeCallStack", Guid.Empty, 0, "OnNativeCallStack", ProviderGuid, ProviderName);
74+
=> new UltraNativeCallstackTraceEvent(value, UltraSamplerConstants.NativeCallStackEventId, 0, "NativeCallStack", Guid.Empty, 0, "NativeCallStack", ProviderGuid, ProviderName);
6175

6276
private static TraceEvent CreateUltraNativeModuleTraceEvent(Action<UltraNativeModuleTraceEvent>? value)
63-
=> new UltraNativeModuleTraceEvent(value, UltraSamplerConstants.NativeModuleEventId, 0, "OnNativeModule", Guid.Empty, 0, "OnNativeModule", ProviderGuid, ProviderName);
77+
=> new UltraNativeModuleTraceEvent(value, UltraSamplerConstants.NativeModuleEventId, 0, "NativeModule", Guid.Empty, 0, "NativeModule", ProviderGuid, ProviderName);
78+
79+
private static TraceEvent CreateUltraNativeThreadStartTraceEvent(Action<UltraNativeThreadStartTraceEvent>? value)
80+
=> new UltraNativeThreadStartTraceEvent(value, UltraSamplerConstants.NativeThreadStartEventId, 0, "NativeThreadStart", Guid.Empty, 0, "NativeThreadStart", ProviderGuid, ProviderName);
81+
82+
private static TraceEvent CreateUltraNativeThreadStopTraceEvent(Action<UltraNativeThreadStopTraceEvent>? value)
83+
=> new UltraNativeThreadStopTraceEvent(value, UltraSamplerConstants.NativeThreadStopEventId, 0, "NativeThreadStop", Guid.Empty, 0, "NativeThreadStop", ProviderGuid, ProviderName);
6484
}

src/Ultra.Sampler/MacOS/MacOSUltraSampler.cs

+20-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal unsafe class MacOSUltraSampler : UltraSampler
1919
private Thread? _samplerThread;
2020
private ulong _samplerThreadId;
2121
private readonly AutoResetEvent _samplerResumeThreadEvent;
22+
private ulong _samplingId;
2223

2324
// Frames information
2425
private const int MaximumFrames = 4096;
@@ -148,7 +149,7 @@ private unsafe void RunImpl()
148149
}
149150
catch (Exception ex)
150151
{
151-
Console.Error.WriteLine($"Ultra-Sampler unexpected exception while sampling: {ex}");
152+
Console.Error.WriteLine($"Ultra-Sampler stopped. Unexpected exception while sampling: {ex}");
152153
}
153154
}
154155

@@ -354,6 +355,18 @@ private unsafe void Sample(MacOS.MacOSLibSystem.mach_port_t rootTask, NativeSamp
354355
continue;
355356
}
356357

358+
// If thread was not active before, simulate a thread start event with its name
359+
if (!_activeThreadIds.Contains(threadInfo.thread_id))
360+
{
361+
var threadName = new ReadOnlySpan<byte>(threadExtendedInfo.pth_name, 64);
362+
var length = threadName.IndexOf((byte)0);
363+
if (length < 0)
364+
{
365+
length = 64;
366+
}
367+
_samplerEventSource.NativeThreadStart(_samplingId, threadInfo.thread_id, length, threadExtendedInfo.pth_name);
368+
}
369+
357370
// -------------------------------------------------------------------
358371
// Suspend the thread
359372
// -------------------------------------------------------------------
@@ -385,7 +398,7 @@ private unsafe void Sample(MacOS.MacOSLibSystem.mach_port_t rootTask, NativeSamp
385398
frameCount -= sameFrameCount;
386399

387400
// Long only the delta frames
388-
samplingDelegate(threadInfo.thread_id, (int)threadExtendedInfo.pth_run_state, (int)threadExtendedInfo.pth_cpu_usage, sameFrameCount, frameCount * sizeof(ulong), (byte*)pFrames);
401+
samplingDelegate(_samplingId, threadInfo.thread_id, (int)threadExtendedInfo.pth_run_state, (int)threadExtendedInfo.pth_cpu_usage, sameFrameCount, frameCount * sizeof(ulong), (byte*)pFrames);
389402
}
390403

391404
// Cleanup threads that are no longer active
@@ -397,11 +410,16 @@ private unsafe void Sample(MacOS.MacOSLibSystem.mach_port_t rootTask, NativeSamp
397410
{
398411
_freeCompressedFramesIndices.Add(compressedFrameIndex);
399412
}
413+
414+
_samplerEventSource.NativeThreadStop(_samplingId, threadInfo.thread_id);
400415
}
401416
}
402417

403418
// Swap the active and current thread ids
404419
(_currentThreadIds, _activeThreadIds) = (_activeThreadIds, _currentThreadIds);
420+
421+
// Increment the sampling id
422+
_samplingId++;
405423
}
406424

407425
private int ComputeSameFrameCount(ulong threadId, int frameCount, ulong* frames)

src/Ultra.Sampler/MacOS/NativeSamplingDelegate.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
namespace Ultra.Sampler.MacOS;
66

7-
internal unsafe delegate void NativeSamplingDelegate(ulong threadId, int threadState, int threadCpuUsage, int previousFrameCount, int deltaFrameSizeInBytes, byte* deltaFrames);
7+
internal unsafe delegate void NativeSamplingDelegate(ulong samplingId, ulong threadId, int threadState, int threadCpuUsage, int previousFrameCount, int deltaFrameSizeInBytes, byte* deltaFrames);

src/Ultra.Sampler/UltraSamplerConstants.cs

+8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ public static class UltraSamplerConstants
1616

1717
public const int NativeModuleEventId = 2;
1818

19+
public const int NativeThreadStartEventId = 3;
20+
21+
public const int NativeThreadStopEventId = 4;
22+
1923
public const int TaskNativeCallStackEventId = 1;
2024

2125
public const int TaskNativeModuleEventId = 2;
26+
27+
public const int TaskNativeThreadStartEventId = 3;
28+
29+
public const int TaskNativeThreadStopEventId = 4;
2230
}

0 commit comments

Comments
 (0)