Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
47231e1
[UserEvents] Add end-to-end runtime test
mdh1418 Nov 3, 2025
963efe1
Merge remote-tracking branch 'upstream/main' into user_events_functio…
mdh1418 Nov 3, 2025
e712df6
Cleanup test props
mdh1418 Nov 3, 2025
2b246be
Add ProcessStartInfo and using keyword
mdh1418 Nov 3, 2025
a7afeb9
Fix process exit detection
mdh1418 Nov 3, 2025
c41916a
Retain nettrace test asset
mdh1418 Nov 3, 2025
963af3f
Address feedback
mdh1418 Nov 7, 2025
005cc94
Fix RecordTrace package resolving
mdh1418 Nov 7, 2025
5d09bd2
Alternative user_events_data access check
mdh1418 Nov 7, 2025
146b348
Fix record-trace startup
mdh1418 Nov 10, 2025
a8d5d97
Detect record-trace unexpected exit and flush output
mdh1418 Nov 10, 2025
f9531d2
Fix record-trace output
mdh1418 Nov 10, 2025
ffc0c16
Reduce enabled keywords
mdh1418 Nov 10, 2025
04f072c
Switch to AllocationSampled Event
mdh1418 Nov 10, 2025
98496bc
Attempt to copy userevents assets for CI
mdh1418 Nov 11, 2025
96efe8e
Enable Helix tests to run record-trace
mdh1418 Nov 12, 2025
fe00510
Add requirements check and upload trace
mdh1418 Nov 18, 2025
368b499
Configure DiagnosticPort creation for RecordTrace
mdh1418 Nov 26, 2025
50b29aa
[Test] Extend UserEvents to reusable TestRunner
mdh1418 Dec 2, 2025
2811831
[Test] Remove eventpipe embedded userevents tests
mdh1418 Dec 2, 2025
23474bb
[TEMP] disable all other pipelines
mdh1418 Nov 26, 2025
4e2febb
Merge remote-tracking branch 'upstream/main' into user_events_functio…
mdh1418 Dec 2, 2025
0acfb99
Include userevents_common in helix payload
mdh1418 Dec 2, 2025
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
3 changes: 2 additions & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
<!-- Not auto-updated. -->
<MicrosoftDiaSymReaderVersion>2.0.0</MicrosoftDiaSymReaderVersion>
<MicrosoftDiaSymReaderNativeVersion>17.10.0-beta1.24272.1</MicrosoftDiaSymReaderNativeVersion>
<TraceEventVersion>3.1.16</TraceEventVersion>
<TraceEventVersion>3.1.28</TraceEventVersion>
<MicrosoftDiagnosticsNetCoreClientVersion>0.2.621003</MicrosoftDiagnosticsNetCoreClientVersion>
<NETStandardLibraryRefVersion>2.1.0</NETStandardLibraryRefVersion>
<NetStandardLibraryVersion>2.0.3</NetStandardLibraryVersion>
Expand All @@ -124,6 +124,7 @@
<!-- Testing -->
<MicrosoftNETCoreCoreDisToolsVersion>1.6.0</MicrosoftNETCoreCoreDisToolsVersion>
<MicrosoftNETTestSdkVersion>17.4.0-preview-20220707-01</MicrosoftNETTestSdkVersion>
<MicrosoftOneCollectRecordTraceVersion>0.1.32221</MicrosoftOneCollectRecordTraceVersion>
<NUnitVersion>3.12.0</NUnitVersion>
<NUnit3TestAdapterVersion>4.5.0</NUnit3TestAdapterVersion>
<CoverletCollectorVersion>6.0.4</CoverletCollectorVersion>
Expand Down
6 changes: 3 additions & 3 deletions eng/pipelines/common/templates/runtimes/run-test-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,19 +233,19 @@ jobs:
# and directly unzip them there after download). Unfortunately the logic to copy
# the native artifacts to the final test folders is dependent on availability of the
# managed test artifacts. This step also generates the final test execution scripts.
- script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) copynativeonly $(logRootNameArg)Native $(testFilterArg) $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(priorityArg) $(librariesOverrideArg) $(codeFlowEnforcementArg)
- script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) copynativeonly -tree tracing $(logRootNameArg)Native $(testFilterArg) $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(priorityArg) $(librariesOverrideArg) $(codeFlowEnforcementArg)
displayName: Copy native test components to test output folder


# Generate test wrappers. This is the step that examines issues.targets to exclude tests.
- script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) buildtestwrappersonly $(logRootNameArg)Wrappers $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(crossArg) $(priorityArg) $(librariesOverrideArg) $(runtimeVariantArg)
- script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) buildtestwrappersonly -tree tracing $(logRootNameArg)Wrappers $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(crossArg) $(priorityArg) $(librariesOverrideArg) $(runtimeVariantArg)
displayName: Generate test wrappers


# Compose the Core_Root folder containing all artifacts needed for running
# CoreCLR tests. This step also compiles the framework using Crossgen2
# in ReadyToRun jobs.
- script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) generatelayoutonly $(logRootNameArg)Layout $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(crossArg) $(priorityArg) $(librariesOverrideArg) $(runtimeVariantArg) -ci
- script: $(Build.SourcesDirectory)/src/tests/build$(scriptExt) generatelayoutonly -tree tracing $(logRootNameArg)Layout $(runtimeFlavorArgs) $(crossgenArg) $(buildConfig) $(archType) $(crossArg) $(priorityArg) $(librariesOverrideArg) $(runtimeVariantArg) -ci
displayName: Generate CORE_ROOT

# Build a Mono LLVM AOT cross-compiler for non-amd64 targets (in this case, just arm64)
Expand Down
2,938 changes: 1,469 additions & 1,469 deletions eng/pipelines/runtime.yml

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions src/tests/Common/helixpublishwitharcade.proj
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,28 @@
<XUnitLogCheckerCommand>$(XUnitLogCheckerHelixPath)XUnitLogChecker$(ExeSuffix) $(XUnitLogCheckerArgs)</XUnitLogCheckerCommand>
</PropertyGroup>

<ItemGroup>
<_ExtraTestExecutablesListFiles Remote="@(_ExtraTestExecutablesListFiles)" />
<_ExtraTestExecutablesListFiles Include="@(_MergedPayloadFiles)"
Condition="$([System.String]::Copy('%(Identity)').ToLower().EndsWith('helix-extra-executables.list'))" />
<_ExtraTestExecutables Remove="@(_ExtraTestExecutables)" />
</ItemGroup>
<ReadLinesFromFile File="%(_ExtraTestExecutablesListFiles.Identity)" Condition="'@(_ExtraTestExecutablesListFiles)' != ''">
<Output TaskParameter="Lines" ItemName="_ExtraTestExecutables" />
</ReadLinesFromFile>
<ItemGroup>
<_ExtraTestExecutables Remove="@(_ExtraTestExecutables)" Condition="'%(Identity)' == ''" />
</ItemGroup>

<ItemGroup>
<!-- We need to ensure that the test run script is marked as executable. -->
<HelixCommandLines Condition="'$(TestWrapperTargetsWindows)' == 'true'" Include="set TEST_HARNESS_STRIPE_TO_EXECUTE=.0.1" />
<HelixCommandLines Condition="'$(TestWrapperTargetsWindows)' != 'true'" Include="export TEST_HARNESS_STRIPE_TO_EXECUTE=.0.1" />
<HelixCommandLines Condition="'$(TestWrapperTargetsWindows)' != 'true'" Include="chmod +x $(_MergedWrapperRunScriptRelative)" />

<!-- Tests may depend on other executables. Copying files to Helix removes execute permissions, so mark them as executable as well. -->
<HelixCommandLines Condition="'$(TestWrapperTargetsWindows)' != 'true' and Exists('$(TestBinDir)%(Identity)')" Include="@(_ExtraTestExecutables->'chmod +x %(Identity)')" />

<HelixCommandLines Include="$(_WorkaroundForNuGetMigrations)" />

<!-- Force assemblies to lazy-load for LLVM AOT test runs to enable using tests that fail at AOT time (and as a result can't be AOTd) -->
Expand Down Expand Up @@ -458,6 +474,7 @@
</ItemGroup>

<Copy UseHardlinksIfPossible="true" SourceFiles="@(ReducedMergedPayloadFilesFinal)" DestinationFiles="@(ReducedMergedPayloadFilesFinal->'$(MergedPayloadsRootDirectory)\$(_MergedWrapperName)\%(FileRelativeToPayloadsRootDirectory)')" Condition="'@(ReducedMergedPayloadFilesFinal)' != ''" />

<WriteLinesToFile File="$(MergedPayloadsRootDirectory)\$(_MergedWrapperName)\HelixCommand.txt" Lines="@(HelixCommandLines)" />
</Target>

Expand Down
1 change: 1 addition & 0 deletions src/tests/build.proj
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<RestoreProjects Include="Common\XHarnessRunnerLibrary\XHarnessRunnerLibrary.csproj" />
<RestoreProjects Include="Common\external\external.csproj" />
<RestoreProjects Include="Common\ilasm\ilasm.ilproj" />
<RestoreProjects Include="tracing\userevents\common\userevents_common.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
61 changes: 61 additions & 0 deletions src/tests/tracing/userevents/basic/basic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Threading;
using Tracing.UserEvents.Tests.Common;
using Microsoft.Diagnostics.Tracing;

namespace Tracing.UserEvents.Tests.Basic
{
public class Basic
{
private static byte[] s_array;

public static int BasicTracee()
{
long startTimestamp = Stopwatch.GetTimestamp();
long targetTicks = Stopwatch.Frequency; // 1s

while (Stopwatch.GetTimestamp() - startTimestamp < targetTicks)
{
s_array = new byte[1024 * 100];
Thread.Sleep(100);
}

return 100;
}

private static Func<EventPipeEventSource, bool> s_traceValidator = (source) =>
{
bool allocationSampledEventFound = false;

source.Dynamic.All += (TraceEvent e) =>
{
if (e.ProviderName == "Microsoft-Windows-DotNETRuntime")
{
// TraceEvent's ClrTraceEventParser does not know about the AllocationSampled Event, so it shows up as "Unknown(303)"
if (e.EventName.StartsWith("Unknown") && e.ID == (TraceEventID)303)
{
allocationSampledEventFound = true;
}
}
};

source.Process();
return allocationSampledEventFound;
};

public static int Main(string[] args)
{
if (args.Length > 0 && args[0].Equals("tracee", System.StringComparison.OrdinalIgnoreCase))
{
return BasicTracee();
}

return UserEventsTestRunner.Run("basic", typeof(Basic).Assembly.Location, s_traceValidator);
}
}
}

19 changes: 19 additions & 0 deletions src/tests/tracing/userevents/basic/basic.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<CLRTestTargetUnsupported Condition="'$(TargetOS)' != 'linux' or ('$(TargetArchitecture)' != 'x64' and '$(TargetArchitecture)' != 'arm64')">true</CLRTestTargetUnsupported>
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<ReferenceXUnitWrapperGenerator>false</ReferenceXUnitWrapperGenerator>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
<ProjectReference Include="../common/userevents_common.csproj" />
</ItemGroup>

<ItemGroup>
<Content Include="basic.script">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>basic.script</TargetPath>
</Content>
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions src/tests/tracing/userevents/basic/basic.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let Microsoft_Windows_DotNETRuntime_flags = new_dotnet_provider_flags();
record_dotnet_provider("Microsoft-Windows-DotNETRuntime", 0x80000000000, 4, Microsoft_Windows_DotNETRuntime_flags);
6 changes: 6 additions & 0 deletions src/tests/tracing/userevents/common/NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="dotnet-diagnostics-tests" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-diagnostics-tests/nuget/v3/index.json" />
</packageSources>
</configuration>
185 changes: 185 additions & 0 deletions src/tests/tracing/userevents/common/UserEventsRequirements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.IO;

namespace Tracing.UserEvents.Tests.Common
{
internal static class UserEventsRequirements
{
private static readonly Version s_minKernelVersion = new(6, 4);
private const string TracefsPath = "/sys/kernel/tracing";
private const string UserEventsDataPath = "/sys/kernel/tracing/user_events_data";
private static readonly Version s_minGlibcVersion = new(2, 35);

internal static bool IsSupported()
{
if (Environment.OSVersion.Version < s_minKernelVersion)
{
Console.WriteLine($"Kernel version '{Environment.OSVersion.Version}' is less than the minimum required '{s_minKernelVersion}'");
return false;
}

return IsGlibcAtLeast(s_minGlibcVersion) &&
IsTracefsMounted() &&
IsUserEventsDataAvailable();
}

private static bool IsTracefsMounted()
{
ProcessStartInfo checkTracefsStartInfo = new();
checkTracefsStartInfo.FileName = "sudo";
checkTracefsStartInfo.Arguments = $"-n test -d {TracefsPath}";
checkTracefsStartInfo.UseShellExecute = false;
checkTracefsStartInfo.RedirectStandardOutput = true;
checkTracefsStartInfo.RedirectStandardError = true;

using Process checkTracefs = new() { StartInfo = checkTracefsStartInfo };
checkTracefs.OutputDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine($"[tracefs-check] {e.Data}");
}
};
checkTracefs.ErrorDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.Error.WriteLine($"[tracefs-check] {e.Data}");
}
};

checkTracefs.Start();
checkTracefs.BeginOutputReadLine();
checkTracefs.BeginErrorReadLine();
checkTracefs.WaitForExit();
if (checkTracefs.ExitCode == 0)
{
return true;
}

Console.WriteLine($"Tracefs not mounted at '{TracefsPath}'.");
return false;
}

private static bool IsUserEventsDataAvailable()
{
ProcessStartInfo checkUserEventsDataStartInfo = new();
checkUserEventsDataStartInfo.FileName = "sudo";
checkUserEventsDataStartInfo.Arguments = $"-n test -e {UserEventsDataPath}";
checkUserEventsDataStartInfo.UseShellExecute = false;
checkUserEventsDataStartInfo.RedirectStandardOutput = true;
checkUserEventsDataStartInfo.RedirectStandardError = true;

using Process checkUserEventsData = new() { StartInfo = checkUserEventsDataStartInfo };
checkUserEventsData.OutputDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine($"[user-events-check] {e.Data}");
}
};
checkUserEventsData.ErrorDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.Error.WriteLine($"[user-events-check] {e.Data}");
}
};

checkUserEventsData.Start();
checkUserEventsData.BeginOutputReadLine();
checkUserEventsData.BeginErrorReadLine();
checkUserEventsData.WaitForExit();
if (checkUserEventsData.ExitCode == 0)
{
return true;
}

Console.WriteLine($"User events data not available at '{UserEventsDataPath}'.");
return false;
}

private static bool IsGlibcAtLeast(Version minVersion)
{
ProcessStartInfo lddStartInfo = new();
lddStartInfo.FileName = "ldd";
lddStartInfo.Arguments = "--version";
lddStartInfo.UseShellExecute = false;
lddStartInfo.RedirectStandardOutput = true;
lddStartInfo.RedirectStandardError = true;

using Process lddProcess = new() { StartInfo = lddStartInfo };
string? detectedVersionLine = null;

lddProcess.OutputDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data) && detectedVersionLine is null)
{
detectedVersionLine = e.Data;
}
};
lddProcess.ErrorDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.Error.WriteLine($"[ldd] {e.Data}");
}
};

try
{
lddProcess.Start();
}
catch (Exception ex)
{
Console.WriteLine($"Failed to start 'ldd --version': {ex.Message}");
return false;
}

lddProcess.BeginOutputReadLine();
lddProcess.BeginErrorReadLine();
lddProcess.WaitForExit();

if (lddProcess.ExitCode != 0)
{
Console.WriteLine($"'ldd --version' exited with code {lddProcess.ExitCode}.");
return false;
}

if (string.IsNullOrEmpty(detectedVersionLine))
{
Console.WriteLine("Could not read glibc version from 'ldd --version' output.");
return false;
}

string[] tokens = detectedVersionLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
Version? glibcVersion = null;
foreach (string token in tokens)
{
if (Version.TryParse(token, out Version? parsedVersion))
{
glibcVersion = parsedVersion;
break;
}
}

if (glibcVersion is null)
{
Console.WriteLine($"Failed to parse glibc version from 'ldd --version' output line: {detectedVersionLine}");
return false;
}

if (glibcVersion < minVersion)
{
Console.WriteLine($"glibc version '{glibcVersion}' is less than required '{minVersion}'.");
return false;
}

return true;
}
}
}
Loading
Loading