Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
14 changes: 14 additions & 0 deletions docs/benchmarking-workflow-dotnet-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,20 @@ recorded in that SDK's `Microsoft.NETCoreSdk.BundledVersions.props`.
`PERFLAB_WASM_PACKAGE_VERSION` should remain unset; that override is reserved
for non-official runtime payloads.

`--wasm-ready-to-run` publishes per-assembly ReadyToRun images. Replace it with
`--wasm-ready-to-run-composite` to compile the benchmark closure into a single
composite image (`PublishReadyToRunComposite=true`, published as
`<entry>.r2r.wasm` and loaded through the boot config's `coreAssembly`
resources). Composite mode requires a WebAssembly SDK with composite support
(dotnet/runtime#134618) and ReadyToRun SDK tasks with wasm output naming
(dotnet/sdk#56395). Until that SDK change is available, set
`PERFLAB_WASM_CROSSGEN2_TASKS_DIR` to a directory containing dotnet/runtime's
`Crossgen2Tasks` shim (`artifacts/bin/Crossgen2Tasks/<Configuration>/` from a
runtime build, or `staging/Crossgen2Tasks/` in the `BrowserWasmCoreCLR` perf
artifact); composite runs then pass it to the WebAssembly SDK through
`Crossgen2SdkOverridePropsPath`/`Crossgen2SdkOverrideTargetsPath`. CI reports
the two modes as `R2RType=r2r` and `R2RType=r2r_composite`.

#### Note about "file ... being used by another process" error

If you are seeing warnings like:
Expand Down
29 changes: 27 additions & 2 deletions eng/pipelines/runtime-wasm-perf-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ jobs:
${{ each parameter in parameters.jobParameters }}:
${{ parameter.key }}: ${{ parameter.value }}

# Run CoreCLR WASM R2R microbenchmarks using the same runtime payload.
# Browser WASM supports per-assembly R2R rather than composite R2R.
# Run CoreCLR WASM per-assembly R2R microbenchmarks using the same runtime payload.
- ${{ if not(startswith(variables['Build.SourceBranch'], 'refs/heads/release')) }}:
- template: /eng/pipelines/common/platform-matrix.yml@${{ parameters.runtimeRepoAlias }}
parameters:
Expand All @@ -182,3 +181,29 @@ jobs:
performanceRepoAlias: ${{ parameters.performanceRepoAlias }}
${{ each parameter in parameters.jobParameters }}:
${{ parameter.key }}: ${{ parameter.value }}

# Run CoreCLR WASM composite R2R microbenchmarks using the same runtime payload.
# The whole benchmark closure is compiled into one <entry>.r2r.wasm image (PublishReadyToRunComposite=true)
# and reported as R2RType=r2r_composite, a history separate from per-assembly R2RType=r2r.
- ${{ if not(startswith(variables['Build.SourceBranch'], 'refs/heads/release')) }}:
- template: /eng/pipelines/common/platform-matrix.yml@${{ parameters.runtimeRepoAlias }}
parameters:
jobTemplate: /eng/pipelines/templates/runtime-perf-job.yml@${{ parameters.performanceRepoAlias }}
buildConfig: release
runtimeFlavor: coreclr
platforms:
- linux_x64
jobParameters:
liveLibrariesBuildConfig: Release
runtimeType: wasm_coreclr
codeGenType: 'wasm'
r2rRunType: 'r2r_composite'
runKind: micro
logicalMachine: 'perfviper'
javascriptEngine: 'v8'
additionalJobIdentifier: coreclr_r2r_composite_v8
downloadSpecificBuild: ${{ parameters.downloadSpecificBuild }}
runtimeRepoAlias: ${{ parameters.runtimeRepoAlias }}
performanceRepoAlias: ${{ parameters.performanceRepoAlias }}
${{ each parameter in parameters.jobParameters }}:
${{ parameter.key }}: ${{ parameter.value }}
4 changes: 2 additions & 2 deletions eng/pipelines/templates/run-performance-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ parameters:
isCompare: false # optional -- If the job is comparing two configurations
onlySanityCheck: false # optional -- Whether to only run a sanity check for pipeline validation, not performance testing
pgoRunType: '' # optional -- PGO run type (e.g. nodynamicpgo)
r2rRunType: '' # optional -- R2R run type (e.g. nor2r)
r2rRunType: '' # optional -- R2R run type (e.g. nor2r, r2r, r2r_composite)
experimentName: '' # optional -- Name of the experiment
javascriptEngine: '' # optional -- JavaScript engine to use
iOSLlvmBuild: false # optional -- Whether to build iOS with LLVM
Expand Down Expand Up @@ -242,7 +242,7 @@ jobs:
- '--cross-build'
- ${{ if ne(parameters.additionalSetupParameters, '') }}:
- '${{ parameters.additionalSetupParameters }}'
- ${{ if and(ne(parameters.wasmWorkloadSource, ''), eq(parameters.runtimeType, 'wasm_coreclr'), eq(parameters.r2rRunType, 'r2r')) }}:
- ${{ if and(ne(parameters.wasmWorkloadSource, ''), eq(parameters.runtimeType, 'wasm_coreclr'), in(parameters.r2rRunType, 'r2r', 'r2r_composite')) }}:
- '--wasm-workload-source "${{ parameters.wasmWorkloadSource }}"'
- template: /eng/pipelines/templates/send-to-helix-step.yml
parameters:
Expand Down
5 changes: 3 additions & 2 deletions scripts/benchmarks_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,11 @@ def main(argv: list[str]):
if args.wasm_workload_source and not (
args.wasm
and args.wasm_runtime_flavor == 'CoreCLR'
and args.wasm_ready_to_run):
and micro_benchmarks.is_wasm_ready_to_run(args)):
raise ValueError(
'--wasm-workload-source requires --wasm '
'--wasm-runtime-flavor CoreCLR --wasm-ready-to-run')
'--wasm-runtime-flavor CoreCLR --wasm-ready-to-run '
'(or --wasm-ready-to-run-composite)')

# Acquire necessary tools (dotnet)
if not args.dotnet_path:
Expand Down
55 changes: 53 additions & 2 deletions scripts/build_runtime_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"build_r2r_interpreter_payload",
"build_wasm_payload",
"build_wasm_coreclr_payload",
"WASM_CROSSGEN2_TASKS_PAYLOAD_DIR",
]


Expand Down Expand Up @@ -322,6 +323,50 @@ def build_wasm_payload(
_set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir], mode=0o664) # rw-rw-r--


# dotnet/runtime's wasm-aware ReadyToRun SDK task shim (Crossgen2Tasks.dll plus the
# Microsoft.NET.CrossGen.props/.targets that load it). Composite CoreCLR WASM R2R needs it until
# dotnet/sdk#56395 reaches the SDK shipped in the BrowserWasmCoreCLR artifact.
WASM_CROSSGEN2_TASKS_PAYLOAD_DIR = "crossgen2-tasks"
WASM_CROSSGEN2_TASKS_FILES = (
"Crossgen2Tasks.dll",
"Microsoft.NET.CrossGen.props",
"Microsoft.NET.CrossGen.targets",
)


def _stage_wasm_crossgen2_tasks(
browser_wasm_coreclr_archive_or_dir: str,
payload_parent_dir: str,
) -> bool:
"""Copy the optional ``staging/Crossgen2Tasks`` shim into the payload.

Returns True when the shim was staged, False when the artifact predates it.
"""
dest_dir = os.path.join(payload_parent_dir, WASM_CROSSGEN2_TASKS_PAYLOAD_DIR)
if (os.path.isdir(browser_wasm_coreclr_archive_or_dir)
and not os.path.isdir(os.path.join(browser_wasm_coreclr_archive_or_dir, "staging", "Crossgen2Tasks"))):
getLogger().info("BrowserWasmCoreCLR artifact does not contain the Crossgen2Tasks shim")
return False

extract_archive_or_copy(
browser_wasm_coreclr_archive_or_dir, dest_dir, prefix="staging/Crossgen2Tasks/"
)
missing = [
name for name in WASM_CROSSGEN2_TASKS_FILES
if not os.path.isfile(os.path.join(dest_dir, name))
]
if len(missing) == len(WASM_CROSSGEN2_TASKS_FILES):
shutil.rmtree(dest_dir, ignore_errors=True)
getLogger().info("BrowserWasmCoreCLR artifact does not contain the Crossgen2Tasks shim")
return False
if missing:
raise ValueError(
f"Incomplete Crossgen2Tasks shim in BrowserWasmCoreCLR artifact, missing: {', '.join(missing)}")

getLogger().info("Staged Crossgen2Tasks shim into %s", dest_dir)
return True


def build_wasm_coreclr_payload(
browser_wasm_coreclr_archive_or_dir: str,
payload_parent_dir: str,
Expand All @@ -331,7 +376,8 @@ def build_wasm_coreclr_payload(
This is a self-contained payload for running CoreCLR WASM benchmarks without
requiring Mono artifacts. The archive/directory layout is expected to contain
a `staging/` folder with `dotnet-none` (SDK) and
`microsoft.netcore.app.runtime.browser-wasm` (CoreCLR runtime pack) subfolders.
`microsoft.netcore.app.runtime.browser-wasm` (CoreCLR runtime pack) subfolders,
and optionally `Crossgen2Tasks` (staged as ``WASM_CROSSGEN2_TASKS_PAYLOAD_DIR``).

Returns:
The shared version of the locally built WebAssembly SDK and Crossgen2 packages.
Expand All @@ -351,6 +397,8 @@ def build_wasm_coreclr_payload(
)
local_package_version = _get_wasm_local_package_version(wasm_built_nugets_dir)
_ensure_wasm_tool_framework_version(wasm_dotnet_dir, local_package_version)
has_crossgen2_tasks = _stage_wasm_crossgen2_tasks(
browser_wasm_coreclr_archive_or_dir, payload_parent_dir)

# Determine version from the runtime pack directory structure
runtime_pack_src = os.path.join(
Expand All @@ -377,7 +425,10 @@ def build_wasm_coreclr_payload(
else:
getLogger().warning("Microsoft.NETCore.App.Ref pack not found – cannot determine version")

_set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir], mode=0o664)
permission_dirs = [wasm_dotnet_dir, wasm_built_nugets_dir]
if has_crossgen2_tasks:
permission_dirs.append(os.path.join(payload_parent_dir, WASM_CROSSGEN2_TASKS_PAYLOAD_DIR))
_set_permissions_recursive(permission_dirs, mode=0o664)
return local_package_version


Expand Down
74 changes: 70 additions & 4 deletions scripts/micro_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,17 @@ def __get_bdn_arguments(user_input: str) -> list[str]:
required=False,
default=False,
action='store_true',
help='Publish CoreCLR WASM benchmarks as ReadyToRun'
help='Publish CoreCLR WASM benchmarks as per-assembly ReadyToRun'
)

parser.add_argument(
'--wasm-ready-to-run-composite',
dest='wasm_ready_to_run_composite',
required=False,
default=False,
action='store_true',
help='Publish CoreCLR WASM benchmarks as a single composite '
'ReadyToRun image (implies ReadyToRun)'
)

parser.add_argument(
Expand Down Expand Up @@ -252,9 +262,21 @@ def __process_arguments(args: list[str]):
return parsed_args


def is_wasm_ready_to_run_composite(args: Any) -> bool:
return bool(getattr(args, 'wasm_ready_to_run_composite', False))


def is_wasm_ready_to_run(args: Any) -> bool:
'''True for either per-assembly or composite CoreCLR WASM ReadyToRun.'''
return bool(args.wasm_ready_to_run) or is_wasm_ready_to_run_composite(args)


def validate_wasm_ready_to_run(args: Any) -> None:
if args.wasm_ready_to_run and (not args.wasm or args.wasm_runtime_flavor != 'CoreCLR'):
raise ArgumentTypeError('--wasm-ready-to-run requires --wasm --wasm-runtime-flavor CoreCLR')
if not args.wasm or args.wasm_runtime_flavor != 'CoreCLR':
if is_wasm_ready_to_run_composite(args):
raise ArgumentTypeError('--wasm-ready-to-run-composite requires --wasm --wasm-runtime-flavor CoreCLR')
if args.wasm_ready_to_run:
raise ArgumentTypeError('--wasm-ready-to-run requires --wasm --wasm-runtime-flavor CoreCLR')


def configure_wasm_ready_to_run(args: Any) -> None:
Expand All @@ -263,7 +285,51 @@ def configure_wasm_ready_to_run(args: Any) -> None:
# BenchmarkDotNet builds generated projects in child processes. MSBuild
# imports environment variables as properties, which lets the generated
# WASM project opt into R2R without requiring a new BDN command-line option.
environ['PERFLAB_WASM_READY_TO_RUN'] = str(args.wasm_ready_to_run).lower()
# Both variables are always written so a stale value from the parent
# environment cannot select a different mode.
environ['PERFLAB_WASM_READY_TO_RUN'] = str(is_wasm_ready_to_run(args)).lower()
environ['PERFLAB_WASM_READY_TO_RUN_COMPOSITE'] = str(is_wasm_ready_to_run_composite(args)).lower()
configure_wasm_crossgen2_sdk_override(args)


WASM_CROSSGEN2_TASKS_DIR_VARIABLE = 'PERFLAB_WASM_CROSSGEN2_TASKS_DIR'
WASM_CROSSGEN2_SDK_OVERRIDE_VARIABLES = (
'Crossgen2SdkOverridePropsPath',
'Crossgen2SdkOverrideTargetsPath',
)


def configure_wasm_crossgen2_sdk_override(args: Any) -> None:
'''Activate dotnet/runtime's wasm-aware ReadyToRun task shim for composite R2R.

Composite CoreCLR WASM R2R needs SDK ReadyToRun tasks that name the owner
image <entry>.r2r.wasm (dotnet/sdk#56395). Until that reaches the SDK in
use, the CoreCLR WASM payload ships the shim and exports its directory in
PERFLAB_WASM_CROSSGEN2_TASKS_DIR. The WebAssembly SDK reads the override
paths during props evaluation, before BenchmarkDotNet imports
MicroBenchmarks.Wasm.targets, so they are passed as environment
properties. TODO: remove with dotnet/runtime#135023.

Inherited override paths are always cleared first so per-assembly R2R
keeps the SDK's tasks and composite never uses a stale shim; set
PERFLAB_WASM_CROSSGEN2_TASKS_DIR to select a shim.
'''
for variable in WASM_CROSSGEN2_SDK_OVERRIDE_VARIABLES:
environ.pop(variable, None)

tasks_dir = environ.get(WASM_CROSSGEN2_TASKS_DIR_VARIABLE)
if not is_wasm_ready_to_run_composite(args) or not tasks_dir:
return
Comment thread
lewing marked this conversation as resolved.

props_path = path.join(tasks_dir, 'Microsoft.NET.CrossGen.props')
targets_path = path.join(tasks_dir, 'Microsoft.NET.CrossGen.targets')
for required in (path.join(tasks_dir, 'Crossgen2Tasks.dll'), props_path, targets_path):
if not path.isfile(required):
raise FileNotFoundError(
f'{WASM_CROSSGEN2_TASKS_DIR_VARIABLE} does not contain {path.basename(required)}: {tasks_dir}')

environ['Crossgen2SdkOverridePropsPath'] = props_path
environ['Crossgen2SdkOverrideTargetsPath'] = targets_path


def __get_benchmarkdotnet_arguments(framework: str, args: Any) -> list[str]:
Expand Down
45 changes: 38 additions & 7 deletions scripts/run_performance_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ def get_pre_commands(
build_config: str,
v8_version: str,
wasm_local_package_version: Optional[str] = None,
wasm_workload_source: Optional[str] = None):
wasm_workload_source: Optional[str] = None,
wasm_crossgen2_tasks_dir: Optional[str] = None):
if os_group == "windows" and runtime_type in ("wasm", "wasm_coreclr"):
raise ValueError(
"WASM performance job prerequisite setup is not supported on Windows")
Expand Down Expand Up @@ -317,6 +318,13 @@ def get_pre_commands(
"PERFLAB_WASM_PACKAGE_VERSION",
wasm_local_package_version)
]
if wasm_crossgen2_tasks_dir:
install_prerequisites += [
set_shell_environment_variable(
os_group,
"PERFLAB_WASM_CROSSGEN2_TASKS_DIR",
wasm_crossgen2_tasks_dir)
]
elif not use_workload_source:
raise ValueError(
"CoreCLR WASM requires either a private runtime payload or "
Expand Down Expand Up @@ -697,6 +705,9 @@ def get_run_configurations(
configurations["R2RType"] = "nor2r"
elif r2r_run_type == "r2r":
configurations["R2RType"] = "r2r"
elif r2r_run_type == "r2r_composite":
# CoreCLR WASM composite R2R gets its own PerfLab history, distinct from per-assembly r2r.
configurations["R2RType"] = "r2r_composite"

if runtime_type == "coreclr_r2r_interpreter":
configurations["R2RType"] = "r2r_interpreter"
Expand Down Expand Up @@ -754,7 +765,8 @@ def get_work_item_command(
wasm_coreclr: bool = False,
wasm_ready_to_run: bool = False,
only_sanity_check: bool = False,
wasm_workload_source: Optional[str] = None):
wasm_workload_source: Optional[str] = None,
wasm_ready_to_run_composite: bool = False):
if os_group == "windows":
work_item_command = [
"python",
Expand Down Expand Up @@ -793,9 +805,11 @@ def get_work_item_command(
]
if wasm_coreclr:
work_item_command += ["--wasm-runtime-flavor", "CoreCLR"]
if wasm_ready_to_run:
if wasm_ready_to_run_composite:
work_item_command += ["--wasm-ready-to-run-composite"]
elif wasm_ready_to_run:
work_item_command += ["--wasm-ready-to-run"]
if wasm_ready_to_run and wasm_workload_source:
if (wasm_ready_to_run or wasm_ready_to_run_composite) and wasm_workload_source:
work_item_command += [
"--wasm-workload-source",
wasm_workload_source,
Expand Down Expand Up @@ -861,6 +875,8 @@ def run_performance_job(args: RunPerformanceJobArgs):
wasm_coreclr = args.runtime_type == "wasm_coreclr"
wasm = args.runtime_type == "wasm" or wasm_coreclr # wasm_coreclr also uses wasm infrastructure
wasm_aot = wasm and is_aot and not wasm_coreclr
if args.r2r_run_type == "r2r_composite" and not wasm_coreclr:
raise Exception("r2r_composite R2R run type is only supported for the wasm_coreclr runtime type")

working_dir = os.path.join(args.performance_repo_dir, "CorrelationStaging") # folder in which the payload and workitem directories will be made
work_item_dir = os.path.join(working_dir, "workitem", "") # Folder in which the work item commands will be run in
Expand Down Expand Up @@ -1021,11 +1037,12 @@ def run_performance_job(args: RunPerformanceJobArgs):

v8_version = ""
wasm_local_package_version = None
helix_wasm_crossgen2_tasks_dir = None
wasm_workload_source = normalize_wasm_workload_source(
args.wasm_workload_source)
wasm_sdk_cohort = (
wasm_coreclr
and args.r2r_run_type == "r2r"
and args.r2r_run_type in ("r2r", "r2r_composite")
and wasm_workload_source is not None)
Comment thread
lewing marked this conversation as resolved.
helix_wasm_workload_source = (
wasm_workload_source if wasm_sdk_cohort else None)
Expand Down Expand Up @@ -1054,6 +1071,18 @@ def run_performance_job(args: RunPerformanceJobArgs):
browser_wasm_coreclr_dir,
payload_dir,
)
if args.r2r_run_type == "r2r_composite":
# Composite needs the artifact's wasm-aware ReadyToRun task shim until
# dotnet/sdk#56395 reaches the artifact's SDK (dotnet/runtime#135023).
if os.path.isdir(os.path.join(payload_dir, WASM_CROSSGEN2_TASKS_PAYLOAD_DIR)):
helix_wasm_crossgen2_tasks_dir = (
f"%HELIX_CORRELATION_PAYLOAD%\\{WASM_CROSSGEN2_TASKS_PAYLOAD_DIR}"
if args.os_group == "windows"
else f"$HELIX_CORRELATION_PAYLOAD/{WASM_CROSSGEN2_TASKS_PAYLOAD_DIR}")
else:
getLogger().warning(
"BrowserWasmCoreCLR artifact has no Crossgen2Tasks shim; composite R2R "
"will use the SDK's ReadyToRun tasks")

elif wasm and not wasm_coreclr:
if args.libraries_download_dir is None:
Expand Down Expand Up @@ -1247,7 +1276,8 @@ def run_performance_job(args: RunPerformanceJobArgs):
args.build_config,
v8_version,
wasm_local_package_version,
helix_wasm_workload_source)
helix_wasm_workload_source,
helix_wasm_crossgen2_tasks_dir)
helix_post_commands = get_post_commands(args.os_group, args.internal, args.runtime_type)

# Point ML.NET at the SSWE model that was pre-downloaded into the correlation payload above, so it
Expand Down Expand Up @@ -1497,7 +1527,8 @@ def get_work_item_command_for_artifact_dir(artifact_dir: str):
wasm_coreclr,
wasm_coreclr and args.r2r_run_type == "r2r",
args.only_sanity_check,
helix_wasm_workload_source)
helix_wasm_workload_source,
wasm_coreclr and args.r2r_run_type == "r2r_composite")

work_item_command = get_work_item_command_for_artifact_dir(bdn_artifacts_directory)
baseline_work_item_command = get_work_item_command_for_artifact_dir(bdn_baseline_artifacts_dir)
Expand Down
Loading
Loading