Superpmi on Microbenchmarks (#47900)
authorKunal Pathak <Kunal.Pathak@microsoft.com>
Sat, 13 Feb 2021 00:43:34 +0000 (16:43 -0800)
committerGitHub <noreply@github.com>
Sat, 13 Feb 2021 00:43:34 +0000 (16:43 -0800)
* Superpmi on Microbenchmarks

* Fix the mch file path name

* distribute the benchmarks in 30 partitions

* Set input directory for benchmarks

* Some fixes to superpmi_benchmarks

Also update the GUID so we don't overwrite the existing collections.

* fix the name of partition_index and partition_count

* Point the core_root path to the superpmi script

* fix python to invoke for setup

* Add verbosity and include all benchmarks

* Fix the benchmarks invocation

* use benchmarks_ci.py script

* run all benchmarks

* fix the performance source code path

* see why dotnet install fails

* Comment all jobs except benchmarks

* update the right fork

* Switch back to doing installing dotnet on azure machine

* Put dotnet in script

* fix dumpMap, revert change in superpmi.py

* Produce artifacts in temp folder

- Disable mcs -strip for now
- Pass the JitName variable

* Experimental: Exit on failure

* Revert "Produce artifacts in temp folder"

This reverts commit afdfbd4b03a684d780ef06f644dba0dcf0621438.

* Use JitName

* Use workitem folder instead of correlation

* fix typo in WorkItemDirectory

* Set the payload directory

* print error message before exiting

* fix some linux issues

* Make dotnet executable

* resolve merge conflicts

* fix typo from merge conflict

* add logging around chmod

* fix the is_windows condition

* cleanup and disable linux arm/arm64

* remove the unwanted parameter

eng/pipelines/coreclr/superpmi.yml
eng/pipelines/coreclr/templates/run-superpmi-job.yml
src/coreclr/inc/jiteeversionguid.h
src/coreclr/scripts/superpmi.proj
src/coreclr/scripts/superpmi.py
src/coreclr/scripts/superpmi_benchmarks.py [new file with mode: 0644]
src/coreclr/scripts/superpmi_setup.py [moved from src/coreclr/scripts/superpmi-setup.py with 76% similarity]

index 163c818..5927afe 100644 (file)
@@ -101,6 +101,28 @@ jobs:
     platforms:
     # Linux tests are built on the OSX machines.
     # - OSX_x64
+    - Linux_arm
+    - Linux_arm64
+    - Linux_x64
+    - windows_x64
+    - windows_x86
+    - windows_arm64
+    - CoreClrTestBuildHost # Either OSX_x64 or Linux_x64
+    helixQueueGroup: ci
+    helixQueuesTemplate: /eng/pipelines/coreclr/templates/helix-queues-setup.yml
+    jobParameters:
+      testGroup: outerloop
+      liveLibrariesBuildConfig: Release
+      collectionType: pmi
+      collectionName: tests
+
+- template: /eng/pipelines/common/platform-matrix.yml
+  parameters:
+    jobTemplate: /eng/pipelines/coreclr/templates/superpmi-job.yml
+    buildConfig: checked
+    platforms:
+    # Linux tests are built on the OSX machines.
+    # - OSX_x64
     # TODO: Linux crossgen2 jobs crash during collection, and need to be investigated.
     # - Linux_arm
     # - Linux_arm64
@@ -123,8 +145,9 @@ jobs:
     platforms:
     # Linux tests are built on the OSX machines.
     # - OSX_x64
-    - Linux_arm
-    - Linux_arm64
+    #TODO: Need special handling of running "benchmark build" from inside TMP folder on helix machine.
+    # - Linux_arm
+    # - Linux_arm64
     - Linux_x64
     - windows_x64
     - windows_x86
@@ -135,6 +158,5 @@ jobs:
     jobParameters:
       testGroup: outerloop
       liveLibrariesBuildConfig: Release
-      collectionType: pmi
-      collectionName: tests
-
+      collectionType: run
+      collectionName: benchmarks
index 7961c30..5478133 100644 (file)
@@ -87,6 +87,9 @@ jobs:
     - ${{ if eq(parameters.collectionName, 'libraries') }}:
       - name: InputDirectory
         value: '$(Core_Root_Dir)'
+    - ${{ if eq(parameters.collectionName, 'benchmarks') }}:
+      - name: InputDirectory
+        value: '$(Core_Root_Dir)'
     - ${{ if eq(parameters.collectionName, 'tests') }}:
       - name: InputDirectory
         value: '$(managedTestArtifactRootFolderPath)'
@@ -103,7 +106,7 @@ jobs:
     steps:
     - ${{ parameters.steps }}
   
-    - script: $(PythonScript) $(Build.SourcesDirectory)/src/coreclr/scripts/superpmi-setup.py -source_directory $(Build.SourcesDirectory) -core_root_directory $(Core_Root_Dir) -arch $(archType) -mch_file_tag $(MchFileTag) -input_directory $(InputDirectory) -collection_name $(CollectionName) -collection_type $(CollectionType) -max_size 50 # size in MB
+    - script: $(PythonScript) $(Build.SourcesDirectory)/src/coreclr/scripts/superpmi_setup.py -source_directory $(Build.SourcesDirectory) -core_root_directory $(Core_Root_Dir) -arch $(archType) -mch_file_tag $(MchFileTag) -input_directory $(InputDirectory) -collection_name $(CollectionName) -collection_type $(CollectionType) -max_size 50 # size in MB
       displayName: ${{ format('SuperPMI setup ({0})', parameters.osGroup) }}
 
       # Run superpmi collection in helix
index 8cc074b..5cfbb06 100644 (file)
 //////////////////////////////////////////////////////////////////////////////////////////////////////////
 //
 
-constexpr GUID JITEEVersionIdentifier = { /* 6ca59d19-13a4-44f7-a184-e9cd1e8f57f8 */
-    0x6ca59d19,
-    0x13a4,
-    0x44f7,
-    { 0xa1, 0x84, 0xe9, 0xcd, 0x1e, 0x8f, 0x57, 0xf8 }
+constexpr GUID JITEEVersionIdentifier = { /* af37688b-d4e5-4a41-a7ee-701728a470aa */
+    0xaf37688b,
+    0xd4e5,
+    0x4a41,
+    {0xa7, 0xee, 0x70, 0x17, 0x28, 0xa4, 0x70, 0xaa}
 };
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////
index 5fa51d7..05ad302 100644 (file)
@@ -1,5 +1,31 @@
 <Project Sdk="Microsoft.DotNet.Helix.Sdk" DefaultTargets="Test">
 
+   <!--
+    This is useful for local testing to print the produced helix items
+    To use this when you are changing how items are produced, uncomment the target
+    and replace the Project item at the top of the file with this:
+    <Project DefaultTargets="printItems">
+
+    Once you've done that you can run this to see the results:
+    dotnet msbuild .\superpmi.proj /v:n
+   -->
+
+   <!-- <PropertyGroup>
+     <HelixTargetQueues>Some_Queue</HelixTargetQueues>
+     <InputArtifacts>D:\git\runtime\workitem\pmiAssembliesDirectory\libraries</InputArtifacts>
+     <MchFileTag>x64.checked</MchFileTag>
+     <CollectionName>benchmarks</CollectionName>
+     <CollectionType>pmi</CollectionType>
+   </PropertyGroup>
+  <Target Name="printItems">
+        <Message Text="@(HelixWorkItem -> 'name: %(HelixWorkItem.Identity)
+     dir: %(HelixWorkItem.PayloadDirectory)
+     pre: %(HelixWorkItem.PreCommands)
+     command: %(HelixWorkItem.Command)
+     post: %(HelixWorkItem.PostCommands)
+     timeout: %(HelixWorkItem.Timeout)  '"/>
+  </Target> -->
+
   <PropertyGroup Condition="'$(AGENT_OS)' == 'Windows_NT'">
     <FileSeparatorChar>\</FileSeparatorChar>
   </PropertyGroup>
     <PmiAssembliesPayload>$(WorkItemDirectory)\pmiAssembliesDirectory</PmiAssembliesPayload>
     <PmiAssembliesDirectory>%HELIX_WORKITEM_PAYLOAD%\binaries</PmiAssembliesDirectory>
     <SuperPMIDirectory>%HELIX_CORRELATION_PAYLOAD%\superpmi</SuperPMIDirectory>
+
+    <!-- Related to Benchmarks -->
+    <PerformanceDirectory>%HELIX_WORKITEM_PAYLOAD%\performance</PerformanceDirectory>
+
     <OutputMchPath>%HELIX_WORKITEM_UPLOAD_ROOT%</OutputMchPath>
     <!-- Workaround until https://github.com/dotnet/arcade/pull/6179 is not available -->
     <HelixResultsDestinationDir>$(BUILD_SOURCESDIRECTORY)\artifacts\helixresults</HelixResultsDestinationDir>
     <PmiAssembliesPayload>$(WorkItemDirectory)/pmiAssembliesDirectory</PmiAssembliesPayload>
     <PmiAssembliesDirectory>$HELIX_WORKITEM_PAYLOAD/binaries</PmiAssembliesDirectory>
     <SuperPMIDirectory>$HELIX_CORRELATION_PAYLOAD/superpmi</SuperPMIDirectory>
+
+    <!-- Related to Benchmarks -->
+    <PerformanceDirectory>$HELIX_WORKITEM_PAYLOAD/performance</PerformanceDirectory>
+
     <OutputMchPath>$HELIX_WORKITEM_UPLOAD_ROOT</OutputMchPath>
     <!-- Workaround until https://github.com/dotnet/arcade/pull/6179 is not available -->
     <HelixResultsDestinationDir>$(BUILD_SOURCESDIRECTORY)/artifacts/helixresults</HelixResultsDestinationDir>
     <WorkItemCommand>$(Python) $(WorkItemCommand) -assemblies $(PmiAssembliesDirectory) -arch $(Architecture) -build_type $(BuildConfig) -core_root $(SuperPMIDirectory)</WorkItemCommand>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(CollectionName)' == 'benchmarks'">
+    <WorkItemCommand>$(Python) $(SuperPMIDirectory)/superpmi_benchmarks.py -performance_directory $(PerformanceDirectory) -superpmi_directory $(SuperPMIDirectory) -core_root $(SuperPMIDirectory) -arch $(Architecture)</WorkItemCommand>
+  </PropertyGroup>
+
   <PropertyGroup>
     <EnableAzurePipelinesReporter>false</EnableAzurePipelinesReporter>
     <EnableXUnitReporter>false</EnableXUnitReporter>
     </HelixCorrelationPayload>
   </ItemGroup>
 
-  <ItemGroup>
+  <ItemGroup Condition="'$(CollectionName)' != 'benchmarks'">
     <PartitionDirectories Include="$([System.IO.Directory]::GetDirectories($(InputArtifacts)))"/>
     <Partition Include="@(PartitionDirectories -> '%(Filename)')" PmiAssemblies="%(Filename)" PartitionId="%(Filename)" />
   </ItemGroup>
 
+  <PropertyGroup>
+    <PartitionCount>30</PartitionCount>
+  </PropertyGroup>
   <ItemGroup>
+    <BDN_Partition Include="Partition0" Index="0" />
+    <BDN_Partition Include="Partition1" Index="1" />
+    <BDN_Partition Include="Partition2" Index="2" />
+    <BDN_Partition Include="Partition3" Index="3" />
+    <BDN_Partition Include="Partition4" Index="4" />
+    <BDN_Partition Include="Partition5" Index="5" />
+    <BDN_Partition Include="Partition6" Index="6" />
+    <BDN_Partition Include="Partition7" Index="7" />
+    <BDN_Partition Include="Partition8" Index="8" />
+    <BDN_Partition Include="Partition9" Index="9" />
+    <BDN_Partition Include="Partition10" Index="10" />
+    <BDN_Partition Include="Partition11" Index="11" />
+    <BDN_Partition Include="Partition12" Index="12" />
+    <BDN_Partition Include="Partition13" Index="13" />
+    <BDN_Partition Include="Partition14" Index="14" />
+    <BDN_Partition Include="Partition15" Index="15" />
+    <BDN_Partition Include="Partition16" Index="16" />
+    <BDN_Partition Include="Partition17" Index="17" />
+    <BDN_Partition Include="Partition18" Index="18" />
+    <BDN_Partition Include="Partition19" Index="19" />
+    <BDN_Partition Include="Partition20" Index="20" />
+    <BDN_Partition Include="Partition21" Index="21" />
+    <BDN_Partition Include="Partition22" Index="22" />
+    <BDN_Partition Include="Partition23" Index="23" />
+    <BDN_Partition Include="Partition24" Index="24" />
+    <BDN_Partition Include="Partition25" Index="25" />
+    <BDN_Partition Include="Partition26" Index="26" />
+    <BDN_Partition Include="Partition27" Index="27" />
+    <BDN_Partition Include="Partition28" Index="28" />
+    <BDN_Partition Include="Partition29" Index="29" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(CollectionName)' != 'benchmarks'">
     <HelixWorkItem Include="@(Partition)">
       <OutputFileName>$(CollectionName).$(CollectionType).%(HelixWorkItem.PartitionId).$(MchFileTag)</OutputFileName>
       <PayloadDirectory>$(PmiAssembliesPayload)$(FileSeparatorChar)$(CollectionName)$(FileSeparatorChar)%(HelixWorkItem.PmiAssemblies)</PayloadDirectory>
       <DownloadFilesFromResults>%(OutputFileName).mch;%(OutputFileName).mch.mct;%(OutputFileName).log</DownloadFilesFromResults>
     </HelixWorkItem>
   </ItemGroup>
-    <!--
-    This is useful for local testing to print the produced helix items
-    To use this when you are changing how items are produced, uncomment the target
-    and replace the Project item at the top of the file with this:
-    <Project DefaultTargets="printItems">
-
-    Once you've done that you can run this to see the results:
-    dotnet msbuild .\superpmi.proj /v:n
-   -->
-<!--    
-   <PropertyGroup>
-     <HelixTargetQueues>Some_Queue</HelixTargetQueues>
-     <InputArtifacts>D:\git\runtime\workitem\pmiAssembliesDirectory\libraries</InputArtifacts>
-     <MchFileTag>x64.checked</MchFileTag>
-     <CollectionName>libraries</CollectionName>
-   </PropertyGroup>
-  <Target Name="printItems">
-        <Message Text="@(HelixWorkItem -> 'name: %(HelixWorkItem.Identity)
-     dir: %(HelixWorkItem.PayloadDirectory)
-     pre: %(HelixWorkItem.PreCommands)
-     command: %(HelixWorkItem.Command)
-     post: %(HelixWorkItem.PostCommands)
-     timeout: %(HelixWorkItem.Timeout)  '"/>
-  </Target> -->
-  
 
+  <ItemGroup Condition="'$(CollectionName)' == 'benchmarks'">
+    <HelixWorkItem Include="@(BDN_Partition)">
+      <OutputFileName>$(CollectionName).$(CollectionType).%(HelixWorkItem.Index).$(MchFileTag)</OutputFileName>
+      <PayloadDirectory>$(WorkItemDirectory)</PayloadDirectory>
+      <Command>$(WorkItemCommand) -partition_count $(PartitionCount) -partition_index %(HelixWorkItem.Index) -output_mch_path $(OutputMchPath)$(FileSeparatorChar)%(OutputFileName).mch -log_file $(OutputMchPath)$(FileSeparatorChar)%(OutputFileName).log</Command>
+      <Timeout>$(WorkItemTimeout)</Timeout>
+      <DownloadFilesFromResults>%(OutputFileName).mch;%(OutputFileName).mch.mct;%(OutputFileName).log</DownloadFilesFromResults>
+    </HelixWorkItem>
+  </ItemGroup>
 </Project>
index e9917d0..5ed0196 100755 (executable)
@@ -543,12 +543,13 @@ class TempDir:
 
         Use with: "with TempDir() as temp_dir" to change to that directory and then automatically
         change back to the original working directory afterwards and remove the temporary
-        directory and its contents (if args.skip_cleanup is False).
+        directory and its contents (if skip_cleanup is False).
     """
 
-    def __init__(self, path=None):
+    def __init__(self, path=None, skip_cleanup=False):
         self.mydir = tempfile.mkdtemp() if path is None else path
         self.cwd = None
+        self._skip_cleanup = skip_cleanup
 
     def __enter__(self):
         self.cwd = os.getcwd()
@@ -557,10 +558,7 @@ class TempDir:
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         os.chdir(self.cwd)
-        # Note: we are using the global `args`, not coreclr_args. This works because
-        # the `skip_cleanup` argument is not processed by CoreclrArguments, but is
-        # just copied there.
-        if not args.skip_cleanup:
+        if not self._skip_cleanup:
             shutil.rmtree(self.mydir)
 
 
@@ -758,7 +756,7 @@ class SuperPMICollect:
         passed = False
 
         try:
-            with TempDir(self.coreclr_args.temp_dir) as temp_location:
+            with TempDir(self.coreclr_args.temp_dir, self.coreclr_args.skip_cleanup) as temp_location:
                 # Setup all of the temp locations
                 self.base_fail_mcl_file = os.path.join(temp_location, "basefail.mcl")
                 self.base_mch_file = os.path.join(temp_location, "base.mch")
@@ -1573,7 +1571,7 @@ class SuperPMIReplayAsmDiffs:
         files_with_asm_diffs = []
         files_with_replay_failures = []
 
-        with TempDir(self.coreclr_args.temp_dir) as temp_location:
+        with TempDir(self.coreclr_args.temp_dir, self.coreclr_args.skip_cleanup) as temp_location:
             logging.debug("")
             logging.debug("Temp Location: %s", temp_location)
             logging.debug("")
diff --git a/src/coreclr/scripts/superpmi_benchmarks.py b/src/coreclr/scripts/superpmi_benchmarks.py
new file mode 100644 (file)
index 0000000..5f9725f
--- /dev/null
@@ -0,0 +1,271 @@
+#!/usr/bin/env python3
+#
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
+#
+#
+# Title: superpmi_benchmarks.py
+#
+# Notes:
+#
+# Script to perform the superpmi collection while executing the Microbenchmarks present
+# in https://github.com/dotnet/performance/tree/master/src/benchmarks/micro.
+
+import argparse
+import re
+import sys
+
+import stat
+from os import path
+from os.path import isfile
+from shutil import copyfile
+from coreclr_arguments import *
+from superpmi import ChangeDir, TempDir
+from superpmi_setup import run_command
+
+# Start of parser object creation.
+is_windows = platform.system() == "Windows"
+parser = argparse.ArgumentParser(description="description")
+
+parser.add_argument("-performance_directory", help="Path to performance directory")
+parser.add_argument("-superpmi_directory", help="Path to superpmi directory")
+parser.add_argument("-core_root", help="Path to Core_Root directory")
+parser.add_argument("-output_mch_path", help="Absolute path to the mch file to produce")
+parser.add_argument("-log_file", help="Name of the log file")
+parser.add_argument("-partition_count", help="Total number of partitions")
+parser.add_argument("-partition_index", help="Partition index to do the collection for")
+parser.add_argument("-arch", help="Architecture")
+
+
+def setup_args(args):
+    """ Setup the args for SuperPMI to use.
+
+    Args:
+        args (ArgParse): args parsed by arg parser
+
+    Returns:
+        args (CoreclrArguments)
+
+    """
+    coreclr_args = CoreclrArguments(args, require_built_core_root=False, require_built_product_dir=False,
+                                    require_built_test_dir=False, default_build_type="Checked")
+
+    coreclr_args.verify(args,
+                        "performance_directory",
+                        lambda performance_directory: os.path.isdir(performance_directory),
+                        "performance_directory doesn't exist")
+
+    coreclr_args.verify(args,
+                        "superpmi_directory",
+                        lambda superpmi_directory: os.path.isdir(superpmi_directory),
+                        "superpmi_directory doesn't exist")
+
+    coreclr_args.verify(args,
+                        "output_mch_path",
+                        lambda output_mch_path: not os.path.isfile(output_mch_path),
+                        "output_mch_path already exist")
+
+    coreclr_args.verify(args,
+                        "log_file",
+                        lambda log_file: True,  # not os.path.isfile(log_file),
+                        "log_file already exist")
+
+    coreclr_args.verify(args,
+                        "core_root",
+                        lambda core_root: os.path.isdir(core_root),
+                        "core_root doesn't exist")
+
+    coreclr_args.verify(args,
+                        "partition_count",
+                        lambda partition_count: partition_count.isnumeric(),
+                        "Unable to set partition_count")
+
+    coreclr_args.verify(args,
+                        "partition_index",
+                        lambda partition_index: partition_index.isnumeric(),
+                        "Unable to set partition_index")
+
+    coreclr_args.verify(args,
+                        "arch",
+                        lambda arch: arch.lower() in ["x86", "x64", "arm", "arm64"],
+                        "Unable to set arch")
+
+    return coreclr_args
+
+
+def make_executable(file_name):
+    """Make file executable by changing the permission
+
+    Args:
+        file_name (string): file to execute
+    """
+    if is_windows:
+        return
+
+    print("Inside make_executable")
+    run_command(["ls", "-l", file_name])
+    os.chmod(file_name,
+             # read+execute for owner
+             (stat.S_IRUSR | stat.S_IXUSR) |
+             # read+execute for group
+             (stat.S_IRGRP | stat.S_IXGRP) |
+             # read+execute for other
+             (stat.S_IROTH | stat.S_IXOTH))
+    run_command(["ls", "-l", file_name])
+
+
+def build_and_run(coreclr_args, output_mch_name):
+    """Build the microbenchmarks and run them under "superpmi collect"
+
+    Args:
+        coreclr_args (CoreClrArguments): Arguments use to drive
+        output_mch_name (string): Name of output mch file name
+    """
+    arch = coreclr_args.arch
+    python_path = sys.executable
+    core_root = coreclr_args.core_root
+    superpmi_directory = coreclr_args.superpmi_directory
+    performance_directory = coreclr_args.performance_directory
+    log_file = coreclr_args.log_file
+    partition_count = coreclr_args.partition_count
+    partition_index = coreclr_args.partition_index
+    dotnet_directory = os.path.join(performance_directory, "tools", "dotnet", arch)
+    dotnet_exe = os.path.join(dotnet_directory, "dotnet")
+
+    artifacts_directory = os.path.join(performance_directory, "artifacts")
+    artifacts_packages_directory = os.path.join(artifacts_directory, "packages")
+    project_file = path.join(performance_directory, "src", "benchmarks", "micro", "MicroBenchmarks.csproj")
+    benchmarks_dll = path.join(artifacts_directory, "MicroBenchmarks.dll")
+
+    if is_windows:
+        shim_name = "%JitName%"
+        corerun_exe = "CoreRun.exe"
+        script_name = "run_microbenchmarks.bat"
+    else:
+        shim_name = "$JitName"
+        corerun_exe = "corerun"
+        script_name = "run_microbenchmarks.sh"
+
+    make_executable(dotnet_exe)
+
+    run_command(
+        [dotnet_exe, "restore", project_file, "--packages",
+         artifacts_packages_directory], _exit_on_fail=True)
+
+    run_command(
+        [dotnet_exe, "build", project_file, "--configuration", "Release",
+         "--framework", "net6.0", "--no-restore", "/p:NuGetPackageRoot=" + artifacts_packages_directory,
+         "-o", artifacts_directory], _exit_on_fail=True)
+
+    collection_command = f"{dotnet_exe} {benchmarks_dll}  --filter \"*\" --corerun {path.join(core_root, corerun_exe)} --partition-count {partition_count} " \
+                         f"--partition-index {partition_index} --envVars COMPlus_JitName:{shim_name} " \
+                         "--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart"
+
+    # Generate the execution script in Temp location
+    with TempDir() as temp_location:
+        script_name = path.join(temp_location, script_name)
+
+        contents = []
+        # Unset the JitName so dotnet process will not fail
+        if is_windows:
+            contents.append("set JitName=%COMPlus_JitName%")
+            contents.append("set COMPlus_JitName=")
+        else:
+            contents.append("#!/bin/bash")
+            contents.append("export JitName=$COMPlus_JitName")
+            contents.append("unset COMPlus_JitName")
+        contents.append(f"pushd {performance_directory}")
+        contents.append(collection_command)
+
+        with open(script_name, "w") as collection_script:
+            collection_script.write(os.linesep.join(contents))
+
+        print()
+        print(f"{script_name} contents:")
+        print("******************************************")
+        print(os.linesep.join(contents))
+        print("******************************************")
+
+        make_executable(script_name)
+
+        run_command([
+            python_path, path.join(superpmi_directory, "superpmi.py"), "collect", "-core_root", core_root,
+            # Disable ReadyToRun so we always JIT R2R methods and collect them
+            "--use_zapdisable",
+            "-output_mch_path", output_mch_name, "-log_file", log_file, "-log_level", "debug",
+            script_name], _exit_on_fail=True)
+
+
+def strip_unrelated_mc(coreclr_args, old_mch_filename, new_mch_filename):
+    """Perform the post processing of produced .mch file by stripping the method contexts
+    that are specific to BenchmarkDotnet boilerplate code and hard
+
+    Args:
+        coreclr_args (CoreclrArguments): Arguments
+        old_mch_filename (string): Name of source .mch file
+        new_mch_filename (string): Name of new .mch file to produce post-processing.
+    """
+    performance_directory = coreclr_args.performance_directory
+    core_root = coreclr_args.core_root
+    methods_to_strip_list = path.join(performance_directory, "methods_to_strip.mcl")
+
+    mcs_exe = path.join(core_root, "mcs")
+    mcs_command = [mcs_exe, "-dumpMap", old_mch_filename]
+
+    # Gather method list to strip
+    (mcs_out, _, return_code) = run_command(mcs_command)
+    if return_code != 0:
+        # If strip command fails, then just copy the old_mch to new_mch
+        print(f"-dumpMap failed. Copying {old_mch_filename} to {new_mch_filename}.")
+        copyfile(old_mch_filename, new_mch_filename)
+        copyfile(old_mch_filename + ".mct", new_mch_filename + ".mct")
+        return
+
+    method_context_list = mcs_out.decode("utf-8").split(os.linesep)
+    filtered_context_list = []
+
+    match_pattern = re.compile('^(\\d+),(BenchmarkDotNet|Perfolizer)')
+    print("Method indices to strip:")
+    for mc_entry in method_context_list:
+        matched = match_pattern.match(mc_entry)
+        if matched:
+            print(matched.group(1))
+            filtered_context_list.append(matched.group(1))
+    print(f"Total {len(filtered_context_list)} methods.")
+
+    with open(methods_to_strip_list, "w") as f:
+        f.write('\n'.join(filtered_context_list))
+
+    # Strip and produce new .mcs file
+    if run_command([mcs_exe, "-strip", methods_to_strip_list, old_mch_filename, new_mch_filename])[2] != 0:
+        # If strip command fails, then just copy the old_mch to new_mch
+        print(f"-strip failed. Copying {old_mch_filename} to {new_mch_filename}.")
+        copyfile(old_mch_filename, new_mch_filename)
+        copyfile(old_mch_filename + ".mct", new_mch_filename + ".mct")
+        return
+
+    # Create toc file
+    run_command([mcs_exe, "-toc", new_mch_filename])
+
+
+def main(main_args):
+    """ Main entry point
+
+    Args:
+        main_args ([type]): Arguments to the script
+    """
+    coreclr_args = setup_args(main_args)
+
+    all_output_mch_name = path.join(coreclr_args.output_mch_path + "_all.mch")
+    build_and_run(coreclr_args, all_output_mch_name)
+    if os.path.isfile(all_output_mch_name):
+        pass
+    else:
+        print("No mch file generated.")
+
+    strip_unrelated_mc(coreclr_args, all_output_mch_name, coreclr_args.output_mch_path)
+
+
+if __name__ == "__main__":
+    args = parser.parse_args()
+    sys.exit(main(args))
similarity index 76%
rename from src/coreclr/scripts/superpmi-setup.py
rename to src/coreclr/scripts/superpmi_setup.py
index 58c4d25..21f51da 100644 (file)
@@ -1,10 +1,10 @@
 #!/usr/bin/env python3
 #
-## Licensed to the .NET Foundation under one or more agreements.
-## The .NET Foundation licenses this file to you under the MIT license.
+# Licensed to the .NET Foundation under one or more agreements.
+# The .NET Foundation licenses this file to you under the MIT license.
 #
 ##
-# Title               : superpmi-setup.py
+# Title               : superpmi_setup.py
 #
 # Notes:
 #
@@ -41,6 +41,7 @@ import tempfile
 from os import linesep, listdir, path, walk
 from os.path import isfile, join, getsize
 from coreclr_arguments import *
+from superpmi import ChangeDir
 
 # Start of parser object creation.
 
@@ -116,6 +117,7 @@ native_binaries_to_ignore = [
 
 MAX_FILES_COUNT = 1500
 
+
 def setup_args(args):
     """ Setup the args for SuperPMI to use.
 
@@ -236,7 +238,7 @@ def first_fit(sorted_by_size, max_size):
         if file_size < max_size:
             for p_index in partitions:
                 total_in_curr_par = sum(n for _, n in partitions[p_index])
-                if (((total_in_curr_par + file_size) < max_size) and (len(partitions[p_index]) < MAX_FILES_COUNT)):
+                if ((total_in_curr_par + file_size) < max_size) and (len(partitions[p_index]) < MAX_FILES_COUNT):
                     partitions[p_index].append(curr_file)
                     found_bucket = True
                     break
@@ -254,20 +256,31 @@ def first_fit(sorted_by_size, max_size):
     return partitions
 
 
-def run_command(command_to_run, _cwd=None):
+def run_command(command_to_run, _exit_on_fail=False):
     """ Runs the command.
 
     Args:
         command_to_run ([string]): Command to run along with arguments.
-        _cmd (string): Current working directory.
+        _exit_on_fail (bool): If it should exit on failure.
+    Returns:
+        (string, string): Returns a tuple of stdout and stderr
     """
     print("Running: " + " ".join(command_to_run))
-    with subprocess.Popen(command_to_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=_cwd) as proc:
-        stdout, stderr = proc.communicate()
-        if len(stdout) > 0:
-            print(stdout.decode("utf-8"))
-        if len(stderr) > 0:
-            print(stderr.decode("utf-8"))
+    command_stdout = ""
+    command_stderr = ""
+    return_code = 1
+    with subprocess.Popen(command_to_run, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
+        command_stdout, command_stderr = proc.communicate()
+        return_code = proc.returncode
+
+        if len(command_stdout) > 0:
+            print(command_stdout.decode("utf-8"))
+        if len(command_stderr) > 0:
+            print(command_stderr.decode("utf-8"))
+        if _exit_on_fail and return_code != 0:
+            print("Command failed. Exiting.")
+            sys.exit(1)
+    return command_stdout, command_stderr, return_code
 
 
 def copy_directory(src_path, dst_path, verbose_output=True, match_func=lambda path: True):
@@ -322,7 +335,8 @@ def copy_files(src_path, dst_path, file_names):
         shutil.copy2(f, dst_path_of_file)
 
 
-def partition_files(src_directory, dst_directory, max_size, exclude_directories=[], exclude_files=native_binaries_to_ignore):
+def partition_files(src_directory, dst_directory, max_size, exclude_directories=[],
+                    exclude_files=native_binaries_to_ignore):
     """ Copy bucketized files based on size to destination folder.
 
     Args:
@@ -345,6 +359,41 @@ def partition_files(src_directory, dst_directory, max_size, exclude_directories=
         index += 1
 
 
+def setup_microbenchmark(workitem_directory, arch):
+    """ Perform setup of microbenchmarks
+
+    Args:
+        workitem_directory (string): Path to work
+        arch (string): Architecture for which dotnet will be installed
+    """
+    performance_directory = path.join(workitem_directory, "performance")
+
+    run_command(
+        ["git", "clone", "--quiet", "--depth", "1", "https://github.com/dotnet/performance", performance_directory])
+
+    with ChangeDir(performance_directory):
+        dotnet_directory = os.path.join(performance_directory, "tools", "dotnet", arch)
+        dotnet_install_script = os.path.join(performance_directory, "scripts", "dotnet.py")
+
+        if not isfile(dotnet_install_script):
+            print("Missing " + dotnet_install_script)
+            return
+
+        run_command(
+            get_python_name() + [dotnet_install_script, "install", "--architecture", arch, "--install-dir", dotnet_directory, "--verbose"])
+
+def get_python_name():
+    """Gets the python name
+
+    Returns:
+        string: Returns the appropriate python name depending on the OS.
+    """
+    if is_windows:
+        return ["py", "-3"]
+    else:
+        return ["python3"]
+
+
 def set_pipeline_variable(name, value):
     """ This method sets pipeline variable.
 
@@ -353,8 +402,8 @@ def set_pipeline_variable(name, value):
         value (string): Value of the variable.
     """
     define_variable_format = "##vso[task.setvariable variable={0}]{1}"
-    print("{0} -> {1}".format(name, value)) # logging
-    print(define_variable_format.format(name, value)) # set variable
+    print("{0} -> {1}".format(name, value))  # logging
+    print(define_variable_format.format(name, value))  # set variable
 
 
 def main(main_args):
@@ -397,55 +446,59 @@ def main(main_args):
     print('Copying {} -> {}'.format(coreclr_args.core_root_directory, superpmi_dst_directory))
     copy_directory(coreclr_args.core_root_directory, superpmi_dst_directory, match_func=acceptable_copy)
 
-    # Clone and build jitutils
-    try:
-        with tempfile.TemporaryDirectory() as jitutils_directory:
-            run_command(
-                ["git", "clone", "--quiet", "--depth", "1", "https://github.com/dotnet/jitutils", jitutils_directory])
-            # Set dotnet path to run bootstrap
-            os.environ["PATH"] = path.join(source_directory, ".dotnet") + os.pathsep + os.environ["PATH"]
-            bootstrap_file = "bootstrap.cmd" if is_windows else "bootstrap.sh"
-            run_command([path.join(jitutils_directory, bootstrap_file)], jitutils_directory)
-
-            copy_files(path.join(jitutils_directory, "bin"), superpmi_dst_directory, [path.join(jitutils_directory, "bin", "pmi.dll")])
-    except PermissionError as pe_error:
-        # Details: https://bugs.python.org/issue26660
-        print('Ignoring PermissionError: {0}'.format(pe_error))
-
     # Workitem directories
     workitem_directory = path.join(source_directory, "workitem")
-    pmiassemblies_directory = path.join(workitem_directory, "pmiAssembliesDirectory")
-
-    # NOTE: we can't use the build machine ".dotnet" to run on all platforms. E.g., the Windows x86 build uses a
-    # Windows x64 .dotnet\dotnet.exe that can't load a 32-bit shim. Thus, we always use corerun from Core_Root to invoke crossgen2.
-    # The following will copy .dotnet to the correlation payload in case we change our mind, and need or want to use it for some scenarios.
-
-    # # Copy ".dotnet" to correlation_payload_directory for crossgen2 job; it is needed to invoke crossgen2.dll
-    # if coreclr_args.collection_type == "crossgen2":
-    #     dotnet_src_directory = path.join(source_directory, ".dotnet")
-    #     dotnet_dst_directory = path.join(correlation_payload_directory, ".dotnet")
-    #     print('Copying {} -> {}'.format(dotnet_src_directory, dotnet_dst_directory))
-    #     copy_directory(dotnet_src_directory, dotnet_dst_directory, verbose_output=False)
-
-    # payload
-    input_artifacts = path.join(pmiassemblies_directory, coreclr_args.collection_name)
-    exclude_directory = ['Core_Root'] if coreclr_args.collection_name == "tests" else []
-    exclude_files = native_binaries_to_ignore
-    if coreclr_args.collection_type == "crossgen2":
-        print('Adding exclusions for crossgen2')
-        # Currently, trying to crossgen2 R2RTest\Microsoft.Build.dll causes a pop-up failure, so exclude it.
-        exclude_files += [ "Microsoft.Build.dll" ]
-    partition_files(coreclr_args.input_directory, input_artifacts, coreclr_args.max_size, exclude_directory, exclude_files)
+    input_artifacts = ""
+
+    if coreclr_args.collection_name == "benchmarks":
+        # Setup microbenchmarks
+        setup_microbenchmark(workitem_directory, arch)
+    else:
+        # Setup for pmi/crossgen runs
+
+        # Clone and build jitutils
+        try:
+            with tempfile.TemporaryDirectory() as jitutils_directory:
+                run_command(
+                    ["git", "clone", "--quiet", "--depth", "1", "https://github.com/dotnet/jitutils", jitutils_directory])
+                # Set dotnet path to run bootstrap
+                os.environ["PATH"] = path.join(source_directory, ".dotnet") + os.pathsep + os.environ["PATH"]
+                bootstrap_file = "bootstrap.cmd" if is_windows else "bootstrap.sh"
+                run_command([path.join(jitutils_directory, bootstrap_file)], jitutils_directory)
+
+                copy_files(path.join(jitutils_directory, "bin"), superpmi_dst_directory, [path.join(jitutils_directory, "bin", "pmi.dll")])
+        except PermissionError as pe_error:
+            # Details: https://bugs.python.org/issue26660
+            print('Ignoring PermissionError: {0}'.format(pe_error))
+
+        # NOTE: we can't use the build machine ".dotnet" to run on all platforms. E.g., the Windows x86 build uses a
+        # Windows x64 .dotnet\dotnet.exe that can't load a 32-bit shim. Thus, we always use corerun from Core_Root to invoke crossgen2.
+        # The following will copy .dotnet to the correlation payload in case we change our mind, and need or want to use it for some scenarios.
+
+        # # Copy ".dotnet" to correlation_payload_directory for crossgen2 job; it is needed to invoke crossgen2.dll
+        # if coreclr_args.collection_type == "crossgen2":
+        #     dotnet_src_directory = path.join(source_directory, ".dotnet")
+        #     dotnet_dst_directory = path.join(correlation_payload_directory, ".dotnet")
+        #     print('Copying {} -> {}'.format(dotnet_src_directory, dotnet_dst_directory))
+        #     copy_directory(dotnet_src_directory, dotnet_dst_directory, verbose_output=False)
+
+        # payload
+        pmiassemblies_directory = path.join(workitem_directory, "pmiAssembliesDirectory")
+        input_artifacts = path.join(pmiassemblies_directory, coreclr_args.collection_name)
+        exclude_directory = ['Core_Root'] if coreclr_args.collection_name == "tests" else []
+        exclude_files = native_binaries_to_ignore
+        if coreclr_args.collection_type == "crossgen2":
+            print('Adding exclusions for crossgen2')
+            # Currently, trying to crossgen2 R2RTest\Microsoft.Build.dll causes a pop-up failure, so exclude it.
+            exclude_files += [ "Microsoft.Build.dll" ]
+        partition_files(coreclr_args.input_directory, input_artifacts, coreclr_args.max_size, exclude_directory, exclude_files)
 
     # Set variables
     print('Setting pipeline variables:')
     set_pipeline_variable("CorrelationPayloadDirectory", correlation_payload_directory)
     set_pipeline_variable("WorkItemDirectory", workitem_directory)
     set_pipeline_variable("InputArtifacts", input_artifacts)
-    if is_windows:
-        set_pipeline_variable("Python", "py -3")
-    else:
-        set_pipeline_variable("Python", "python3")
+    set_pipeline_variable("Python", ' '.join(get_python_name()))
     set_pipeline_variable("Architecture", arch)
     set_pipeline_variable("Creator", creator)
     set_pipeline_variable("Queue", helix_queue)