Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-t4": {
"version": "3.0.0",
"commands": [
"t4"
],
"rollForward": false
}
}
}
12 changes: 12 additions & 0 deletions DynamicProbes.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "benchmark\Benchmark.csproj", "{2F9E49F9-1B8C-4170-8FF1-313BEF57D91C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "tests\UnitTests\UnitTests.csproj", "{C530FC8B-FA78-40E7-881E-3EDF52EEB59E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicProbesDemo", "eg\DynamicProbesDemo.csproj", "{D11BF120-04A6-434D-A19E-5CD19D6ABFD6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -30,6 +34,14 @@ Global
{2F9E49F9-1B8C-4170-8FF1-313BEF57D91C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F9E49F9-1B8C-4170-8FF1-313BEF57D91C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F9E49F9-1B8C-4170-8FF1-313BEF57D91C}.Release|Any CPU.Build.0 = Release|Any CPU
{C530FC8B-FA78-40E7-881E-3EDF52EEB59E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C530FC8B-FA78-40E7-881E-3EDF52EEB59E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C530FC8B-FA78-40E7-881E-3EDF52EEB59E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C530FC8B-FA78-40E7-881E-3EDF52EEB59E}.Release|Any CPU.Build.0 = Release|Any CPU
{D11BF120-04A6-434D-A19E-5CD19D6ABFD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D11BF120-04A6-434D-A19E-5CD19D6ABFD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D11BF120-04A6-434D-A19E-5CD19D6ABFD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D11BF120-04A6-434D-A19E-5CD19D6ABFD6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
55 changes: 31 additions & 24 deletions benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using LibstapsdtPinvokes;
using DynamicProbes;

namespace Benchmark;

Expand All @@ -10,32 +10,39 @@ namespace Benchmark;
[SimpleJob(RuntimeMoniker.NativeAot80)]
public class UnobservedProbeFireBenchmarks
{
nint provider;
nint probe1;
nint probe2;
nint probe3;
nint probe4;
nint probe5;
nint probe6;
ILoadedProvider? provider;
Probe<Int64Arg> probe1;
Probe<Int64Arg, Int64Arg> probe2;
Probe<Int64Arg, Int64Arg, Int64Arg> probe3;
Probe<Int64Arg, Int64Arg, Int64Arg, Int64Arg> probe4;
Probe<Int64Arg, Int64Arg, Int64Arg, Int64Arg, Int64Arg> probe5;
Probe<Int64Arg, Int64Arg, Int64Arg, Int64Arg, Int64Arg, Int64Arg> probe6;

[GlobalSetup]
public void GlobalSetup()
{
this.provider = Libstapsdt.ProviderInit("myprovider");
this.probe1 = Libstapsdt.ProviderAddProbe(this.provider, "myprobe1", ArgType.Int64);
this.probe2 = Libstapsdt.ProviderAddProbe(this.provider, "myprobe2", ArgType.Int64, ArgType.Int64);
this.probe3 = Libstapsdt.ProviderAddProbe(this.provider, "myprobe3", ArgType.Int64, ArgType.Int64, ArgType.Int64);
this.probe4 = Libstapsdt.ProviderAddProbe(this.provider, "myprobe4", ArgType.Int64, ArgType.Int64, ArgType.Int64, ArgType.Int64);
this.probe5 = Libstapsdt.ProviderAddProbe(this.provider, "myprobe5", ArgType.Int64, ArgType.Int64, ArgType.Int64, ArgType.Int64, ArgType.Int64);
this.probe6 = Libstapsdt.ProviderAddProbe(this.provider, "myprobe6", ArgType.Int64, ArgType.Int64, ArgType.Int64, ArgType.Int64, ArgType.Int64, ArgType.Int64);
_ = Libstapsdt.ProviderLoad(this.provider);
var provider = Provider.Init("myprovider");
try
{
this.probe1 = provider.AddProbe<Int64Arg>("myprobe1");
this.probe2 = provider.AddProbe<Int64Arg, Int64Arg>("myprobe2");
this.probe3 = provider.AddProbe<Int64Arg, Int64Arg, Int64Arg>("myprobe3");
this.probe4 = provider.AddProbe<Int64Arg, Int64Arg, Int64Arg, Int64Arg>("myprobe4");
this.probe5 = provider.AddProbe<Int64Arg, Int64Arg, Int64Arg, Int64Arg, Int64Arg>("myprobe5");
this.probe6 = provider.AddProbe<Int64Arg, Int64Arg, Int64Arg, Int64Arg, Int64Arg, Int64Arg>("myprobe6");
this.provider = provider.Load();
}
catch
{
provider.Dispose();
throw;
}
}

[GlobalCleanup]
public void GlobalCleanup()
{
_ = Libstapsdt.ProviderUnload(this.provider);
Libstapsdt.ProviderDestroy(this.provider);
this.provider?.Dispose();
}

const long Arg1 = 1234567890123456789;
Expand All @@ -47,22 +54,22 @@ public void GlobalCleanup()


[Benchmark]
public void ArgCount1() => Libstapsdt.ProbeFire(this.probe1, Arg1);
public void ArgCount1() => this.probe1.Fire(Arg1);

[Benchmark]
public void ArgCount2() => Libstapsdt.ProbeFire(this.probe2, Arg1, Arg2);
public void ArgCount2() => this.probe2.Fire(Arg1, Arg2);

[Benchmark]
public void ArgCount3() => Libstapsdt.ProbeFire(this.probe3, Arg1, Arg2, Arg3);
public void ArgCount3() => this.probe3.Fire(Arg1, Arg2, Arg3);

[Benchmark]
public void ArgCount4() => Libstapsdt.ProbeFire(this.probe4, Arg1, Arg2, Arg3, Arg4);
public void ArgCount4() => this.probe4.Fire(Arg1, Arg2, Arg3, Arg4);

[Benchmark]
public void ArgCount5() => Libstapsdt.ProbeFire(this.probe5, Arg1, Arg2, Arg3, Arg4, Arg5);
public void ArgCount5() => this.probe5.Fire(Arg1, Arg2, Arg3, Arg4, Arg5);

[Benchmark]
public void ArgCount6() => Libstapsdt.ProbeFire(this.probe6, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6);
public void ArgCount6() => this.probe6.Fire(Arg1, Arg2, Arg3, Arg4, Arg5, Arg6);
}

public static class Program
Expand Down
15 changes: 15 additions & 0 deletions eg/DynamicProbesDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\src\DynamicProbes.csproj" />
</ItemGroup>

</Project>
19 changes: 7 additions & 12 deletions src/Program.cs → eg/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,19 @@

using System.Globalization;
using System.Runtime.InteropServices;
using LibstapsdtPinvokes;
using DynamicProbes;

try
{
var providerName = "myprovider";
var probeName = "myprobe";

var provider = Libstapsdt.ProviderInit(providerName);
using var provider = Provider.Init(providerName);

_ = Libstapsdt.ProviderUseMemfd(provider, MemfdOption.Enabled);
// _ = Libstapsdt.ProviderUseMemfd(provider, MemfdOption.Enabled);

var probe = Libstapsdt.ProviderAddProbe(provider, probeName, ArgType.Int64, ArgType.UInt64);
if (probe == IntPtr.Zero)
throw new Exception("Could not initialize the probe");

var res = Libstapsdt.ProviderLoad(provider);
if (res != 0)
throw new Exception("Could not load provider");
var probe = provider.AddProbe<Int64Arg, IntPtrArg>(probeName);
_ = provider.Load();

Console.WriteLine("Ready! Trace me with:");
Console.WriteLine($$""" sudo bpftrace -p {{Environment.ProcessId}} -e 'usdt:*:myprovider:myprobe { printf("Fired values: %ld %s\n", arg0, str(arg1)); }'""");
Expand All @@ -32,13 +27,13 @@
var isoTimeStringPtr = Marshal.StringToCoTaskMemUTF8(isoTimeString);
try
{
Libstapsdt.ProbeFire(probe, val, isoTimeStringPtr);
probe.Active?.Fire(val, isoTimeStringPtr);
}
finally
{
Marshal.FreeCoTaskMem(isoTimeStringPtr);
}
Console.WriteLine("Probe fired! Probe is currently {0}", Libstapsdt.ProbeIsEnabled(probe) ? "watched" : "not watched");
Console.WriteLine("Probe fired! Probe is currently {0}", probe.IsEnabled ? "watched" : "not watched");
Thread.Sleep(500);
}
}
Expand Down
137 changes: 137 additions & 0 deletions src/Args.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
namespace DynamicProbes;

public enum ArgType // NOTE! Keep in sync with Libstapsdt.ArgType!
{
NoArg = Libstapsdt.ArgType.NoArg,
#pragma warning disable CA1720 // Identifier contains type name (by-design for familiarity)
UInt8 = Libstapsdt.ArgType.UInt8,
Int8 = Libstapsdt.ArgType.Int8,
UInt16 = Libstapsdt.ArgType.UInt16,
Int16 = Libstapsdt.ArgType.Int16,
UInt32 = Libstapsdt.ArgType.UInt32,
Int32 = Libstapsdt.ArgType.Int32,
UInt64 = Libstapsdt.ArgType.UInt64,
Int64 = Libstapsdt.ArgType.Int64,
#pragma warning disable CA1720 // Identifier contains type name
}

public interface IArgType
{
static abstract ArgType ArgType { get; }
}

public interface IFireArgLong
{
long UncheckedValue { get; }

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always call the function from the C library with the long arguments, even if the probe was registered as Probe<Int8, Int8>.

I wonder if it's safe to pass the function parameters to C as 4-byte types; and if yes, why allow registering the probe as unit8 in the first place?

}

#pragma warning disable CA2225 // Operator overloads have named alternates (not needed)

/// <summary>
/// <see langword="byte"/> as <see cref="ArgType.UInt8"/>.
/// </summary>
public readonly record struct UInt8Arg(byte Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.UInt8;
public static implicit operator UInt8Arg(byte value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="sbyte"/> as <see cref="ArgType.Int8"/>.
/// </summary>
public readonly record struct Int8Arg(sbyte Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.Int8;
public static implicit operator Int8Arg(sbyte value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="ushort"/> as <see cref="ArgType.UInt16"/>.
/// </summary>
public readonly record struct UInt16Arg(ushort Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.UInt16;
public static implicit operator UInt16Arg(ushort value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="short"/> as <see cref="ArgType.Int16"/>.
/// </summary>
public readonly record struct Int16Arg(short Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.Int16;
public static implicit operator Int16Arg(short value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="uint"/> as <see cref="ArgType.UInt32"/>.
/// </summary>
public readonly record struct UInt32Arg(uint Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.UInt32;
public static implicit operator UInt32Arg(uint value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="int"/> as <see cref="ArgType.Int32"/>.
/// </summary>
public readonly record struct Int32Arg(int Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.Int32;
public static implicit operator Int32Arg(int value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="ulong"/> as <see cref="ArgType.UInt64"/>.
/// </summary>
public readonly record struct UInt64Arg(ulong Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.UInt64;
public static implicit operator UInt64Arg(ulong value) => new(value);
long IFireArgLong.UncheckedValue => unchecked((long)Value);
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="long"/> as <see cref="ArgType.Int64"/>.
/// </summary>
public readonly record struct Int64Arg(long Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.Int64;
public static implicit operator Int64Arg(long value) => new(value);
long IFireArgLong.UncheckedValue => Value;
public override string ToString() => $"{Value}";
}

/// <summary>
/// <see langword="nint"/> as an argument of type <see cref="ArgType.UInt64"/>.
/// </summary>
public readonly record struct IntPtrArg(nint Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.UInt64;
public static implicit operator IntPtrArg(nint value) => new(value);
long IFireArgLong.UncheckedValue => Value;
}

/// <summary>
/// <see langword="bool"/> as an argument of type <see cref="ArgType.Int32"/>.
/// </summary>
public readonly record struct BoolArg(bool Value) : IArgType, IFireArgLong
{
public static ArgType ArgType => ArgType.Int32;
public static implicit operator BoolArg(bool value) => new(value);
long IFireArgLong.UncheckedValue => Value ? 1 : 0;
public override string ToString() => Value.ToString();
}
32 changes: 31 additions & 1 deletion src/DynamicProbes.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<ImplicitUsings>enable</ImplicitUsings>
Expand All @@ -21,4 +20,35 @@
<Compile Remove="$(GeneratedFolder)/**/*.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Update="Probes.g.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Probes.g.cs</LastGenOutput>
</None>
</ItemGroup>

<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>

<ItemGroup>
<Compile Update="Probes.g.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Probes.g.tt</DependentUpon>
</Compile>
</ItemGroup>
Comment on lines +30 to +47

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this is metainformation about the sources for the build task, but it won't regenerate if the template changes. At least on my Ubuntu, dotnet build doesn't trigger Probes.g.cs regeneration.

One could define a pre-build task to do exactly that. But it will be noisy if we keep the generated at {datetime}... line in the generated file.

CI can still check if the generated source is in sync with the template by making an exception for generated at {datetime} in the diff.

What do you think? Is there a best practice for keeping T4 templates in sync with the generated files?


<ItemGroup>
<AdditionalFiles Include="PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI.Unshipped.txt" />
</ItemGroup>

</Project>
Loading