-
Notifications
You must be signed in to change notification settings - Fork 300
Expand file tree
/
Copy pathrun_performance_job.py
More file actions
1560 lines (1334 loc) · 73.9 KB
/
Copy pathrun_performance_job.py
File metadata and controls
1560 lines (1334 loc) · 73.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from logging import getLogger
import re
from dataclasses import dataclass, field
from datetime import timedelta
from glob import glob
import hashlib
import json
import os
import shutil
from subprocess import CalledProcessError
import sys
import tempfile
import time
from traceback import format_exc
import urllib.request
import xml.etree.ElementTree as ET
from typing import Any, Optional
from build_runtime_payload import *
import ci_setup
from performance.common import RunCommand, set_environment_variable
from performance.logger import setup_loggers
from send_to_helix import PerfSendToHelixArgs, perf_send_to_helix
DEFAULT_BUILD_CONFIG = "Release"
def output_counters_for_crank(reports: list[Any]):
print("#StartJobStatistics")
statistics: dict[str, list[Any]] = {
"metadata": [],
"measurements": []
}
for report in reports:
for test in report["tests"]:
for counter in test["counters"]:
measurement_name = f"benchmarkdotnet/{test['name']}/{counter['name']}"
for result in counter["results"]:
statistics["measurements"].append({
"name": measurement_name,
"value": result
})
if counter["topCounter"] == True:
statistics["metadata"].append({
"source": "BenchmarkDotNet",
"name": measurement_name,
"aggregate": "avg",
"reduce": "avg",
"format": "n0",
"shortDescription": f"{test['name']} ({counter['metricName']})"
})
statistics["metadata"] = sorted(statistics["metadata"], key=lambda m: m["name"])
print(json.dumps(statistics))
print("#EndJobStatistics")
@dataclass
class RunPerformanceJobArgs:
run_kind: str
architecture: str
os_group: str
os_distro: Optional[str] = None
logical_machine: Optional[str] = None
queue: Optional[str] = None
machine_pool: Optional[str] = None
framework: Optional[str] = None
performance_repo_dir: str = "."
runtime_repo_dir: Optional[str] = None
core_root_dir: Optional[str] = None
baseline_core_root_dir: Optional[str] = None
mono_dotnet_dir: Optional[str] = None
libraries_download_dir: Optional[str] = None
versions_props_path: Optional[str] = None
browser_versions_props_path: Optional[str] = None
built_app_dir: Optional[str] = None
extra_bdn_args: Optional[str] = None
run_categories: str = 'Libraries Runtime'
helix_access_token: Optional[str] = os.environ.get("HelixAccessToken")
os_sub_group: Optional[str] = None
project_file: Optional[str] = None
partition_count: Optional[int] = None
build_repository_name: str = os.environ.get("BUILD_REPOSITORY_NAME", "dotnet/performance")
build_source_branch: str = os.environ.get("BUILD_SOURCEBRANCH", "main")
build_number: str = os.environ.get("BUILD_BUILDNUMBER", "local")
build_definition_name: Optional[str] = os.environ.get("BUILD_DEFINITIONNAME")
build_reason: Optional[str] = os.environ.get("BUILD_REASON")
internal: bool = False
pgo_run_type: Optional[str] = None
physical_promotion_run_type: Optional[str] = None
r2r_run_type: Optional[str] = None
experiment_name: Optional[str] = None
codegen_type: str = "JIT"
linking_type: str = "dynamic"
runtime_type: str = "coreclr"
affinity: Optional[str] = "0"
run_env_vars: dict[str, str] = field(default_factory=dict[str, str])
is_scenario: bool = False
runtime_flavor: Optional[str] = None
local_build: bool = False
compare: bool = False
only_sanity_check: bool = False
ios_llvm_build: bool = False
ios_strip_symbols: bool = False
javascript_engine: str = "NoJS"
send_to_helix: bool = False
channel: Optional[str] = None
perf_repo_hash: Optional[str] = os.environ.get("BUILD_SOURCEVERSION")
performance_repo_ci: bool = False
use_local_commit_time: bool = False
javascript_engine_path: Optional[str] = None
maui_version: Optional[str] = None
pdn_path: Optional[str] = None
os_version: Optional[str] = None
dotnet_version_link: Optional[str] = None
target_csproj: Optional[str] = None
build_config: str = DEFAULT_BUILD_CONFIG
live_libraries_build_config: Optional[str] = None
cross_build: bool = False
# Subdirectory (inside the Helix correlation payload) that holds the pre-downloaded ML.NET resources.
# On the Helix machine this is referenced as <HELIX_CORRELATION_PAYLOAD>/mlnet-resources.
MLNET_RESOURCES_PAYLOAD_SUBDIR = "mlnet-resources"
# Known-good SHA256 of the SSWE word-embedding model (sentiment.emd, 73,674,434 bytes). The asset is
# a fixed pretrained model and is not expected to change; validating the hash guarantees we shipped a
# complete, uncorrupted file (a truncated or proxy-mangled response won't match). If the upstream
# asset is ever intentionally updated, recompute and update this value.
MLNET_SSWE_MODEL_SHA256 = "a8062ef5d3a1ffc079a2b3c439a5533279f4ac6d85882ca8488fb8ff239fefdd"
def try_provision_mlnet_resources(payload_dir: str) -> bool:
"""
Pre-download the ML.NET SSWE word-embedding model into the correlation payload.
StochasticDualCoordinateAscentClassifierBench.TrainSentiment applies a pretrained word embedding
('sentiment.emd', ~70 MB) that ML.NET otherwise downloads from https://aka.ms/mlnet-resources at
benchmark runtime. That download stalls on the Helix machines and hangs the whole mlnet work item
until it times out. Downloading it here on the build agent (reliable connectivity) and pointing
MICROSOFTML_RESOURCE_PATH at <payload>/mlnet-resources lets ML.NET load it from disk instead.
ML.NET resolves the model at <MICROSOFTML_RESOURCE_PATH>/Text/Sswe/sentiment.emd.
Best-effort: returns False on failure so the caller skips the env var and the previous
(runtime-download) behavior is left unchanged.
"""
resource_root = os.path.join(payload_dir, MLNET_RESOURCES_PAYLOAD_SUBDIR)
dest = os.path.join(resource_root, "Text", "Sswe", "sentiment.emd")
os.makedirs(os.path.dirname(dest), exist_ok=True)
# The direct blob URL is the redirect target of the aka.ms link; prefer it to avoid the redirect,
# and fall back to the aka.ms link in case the blob path ever changes.
urls = [
"https://mlpublicassets.blob.core.windows.net/assets/Text/Sswe/sentiment.emd",
"https://aka.ms/mlnet-resources/Text/Sswe/sentiment.emd",
]
last_error: Optional[Exception] = None
max_attempts = 3
for attempt in range(1, max_attempts + 1):
for url in urls:
tmp_dest = dest + ".tmp"
try:
getLogger().info(f"Downloading ML.NET SSWE model from {url} (attempt {attempt})")
with urllib.request.urlopen(url, timeout=60) as response:
with open(tmp_dest, "wb") as f:
shutil.copyfileobj(response, f)
# The model is a fixed asset, so verify the exact hash to reject any truncated or
# corrupted download before it can be shipped in the payload.
sha256 = hashlib.sha256()
with open(tmp_dest, "rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
sha256.update(chunk)
actual_hash = sha256.hexdigest()
if actual_hash != MLNET_SSWE_MODEL_SHA256:
raise Exception(f"sha256 {actual_hash} does not match expected {MLNET_SSWE_MODEL_SHA256}")
os.replace(tmp_dest, dest)
getLogger().info(f"Downloaded and verified ML.NET SSWE model to {dest}")
return True
except Exception as e:
last_error = e
getLogger().warning(f"Failed to download ML.NET SSWE model from {url}: {e}")
if os.path.exists(tmp_dest):
os.remove(tmp_dest)
# Only wait between attempts, not after the final one.
if attempt < max_attempts:
time.sleep(10)
getLogger().warning(
"Could not pre-provision the ML.NET SSWE model into the payload after retries "
f"(last error: {last_error}); ML.NET will attempt to download it at benchmark runtime.")
return False
def get_pre_commands(
os_group: str,
os_distro: Optional[str],
internal: bool,
runtime_type: str,
codegen_type: str,
build_config: str,
v8_version: str):
helix_pre_commands: list[str] = []
# Remember the previous PYTHONPATH that was set so it can be restored in the post commands
if os_group == "windows":
helix_pre_commands += ["set ORIGPYPATH=%PYTHONPATH%"]
else:
helix_pre_commands += ["export ORIGPYPATH=$PYTHONPATH"]
# Create separate list of commands to handle the next part.
# On non-Windows, these commands are chained together with && so they will stop if any fail
install_prerequisites: list[str] = []
if internal:
# Run inside a python venv
if os_group == "windows":
install_prerequisites += [
"(py -3 -c \"exit(1 if __import__('sys').version_info[:2] == (3, 13) and 'experimental free-threading' in __import__('sys').version.lower() else 0)\" && py -3 -m venv %HELIX_WORKITEM_ROOT%\\.venv || py -3.13 -m venv %HELIX_WORKITEM_ROOT%\\.venv)",
"call %HELIX_WORKITEM_ROOT%\\.venv\\Scripts\\activate.bat",
"echo on" # venv activate script turns echo off, so turn it back on
]
else:
if os_group != "osx":
if os_distro == "azurelinux":
install_prerequisites += [
"sudo tdnf -y install python3-pip"
]
else:
install_prerequisites += [
'echo "** Waiting for dpkg to unlock (up to 2 minutes) **"',
'timeout 2m bash -c \'while sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do if [ -z "$printed" ]; then echo "Waiting for dpkg lock to be released... Lock is held by: $(ps -o cmd= -p $(sudo fuser /var/lib/dpkg/lock-frontend))"; printed=1; fi; echo "Waiting 5 seconds to check again"; sleep 5; done;\'',
"sudo apt-get remove -y lttng-modules-dkms", # https://github.com/dotnet/runtime/pull/101142
"sudo apt-get -y install python3-pip"
]
install_prerequisites += [
"python3 -m venv $HELIX_WORKITEM_ROOT/.venv",
". $HELIX_WORKITEM_ROOT/.venv/bin/activate"
]
# Clear the PYTHONPATH first so that modules installed elsewhere are not used
# Note: On Windows, 'set PYTHONPATH=' must be a separate command, not chained with &&,
# otherwise Python fails with "OSError: failed to make path absolute"
if os_group == "windows":
helix_pre_commands += ["set PYTHONPATH="]
else:
install_prerequisites += ["export PYTHONPATH="]
# Install python pacakges needed to upload results to azure storage
install_prerequisites += [
f"python -m pip install -U pip",
f"python -m pip install cryptography==46.0.3",
f"python -m pip install azure.storage.blob==12.13.0",
f"python -m pip install azure.storage.queue==12.4.0",
f"python -m pip install azure.identity==1.16.1",
f"python -m pip install urllib3==1.26.19",
f"python -m pip install opentelemetry-api==1.23.0",
f"python -m pip install opentelemetry-sdk==1.23.0",
f"python -m pip install six==1.17.0",
]
# Install prereqs for NodeJS https://github.com/dotnet/runtime/pull/40667
# TODO: is this still needed? It seems like it was added to support wasm which is already setting up everything
if os_group != "windows" and os_group != "osx":
if os_distro == "azurelinux":
install_prerequisites += [
"sudo tdnf -y install curl ca-certificates"
]
else:
install_prerequisites += [
"sudo apt-get update",
"sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates"
]
# Set up everything needed for WASM runs (both Mono and CoreCLR)
if runtime_type in ("wasm", "wasm_coreclr"):
if os_distro == "azurelinux":
# Azure Linux uses tdnf package manager
install_prerequisites += [
"export RestoreAdditionalProjectSources=$HELIX_CORRELATION_PAYLOAD/built-nugets",
"sudo tdnf -y update",
"sudo tdnf -y remove nodejs",
"sudo tdnf -y install ca-certificates curl gnupg nodejs npm",
f"test -n \"{v8_version}\"",
"npm install --prefix $HELIX_WORKITEM_ROOT jsvu -g",
f"$HELIX_WORKITEM_ROOT/bin/jsvu --os=linux64 v8@{v8_version}",
f"export V8_ENGINE_PATH=~/.jsvu/bin/v8-{v8_version}",
"${V8_ENGINE_PATH} -e 'console.log(`V8 version: ${this.version()}`)'"
]
else:
install_prerequisites += [
"export RestoreAdditionalProjectSources=$HELIX_CORRELATION_PAYLOAD/built-nugets",
'echo "** Waiting for dpkg to unlock (up to 2 minutes) **"',
'timeout 2m bash -c \'while sudo fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do if [ -z "$printed" ]; then echo "Waiting for dpkg lock to be released... Lock is held by: $(ps -o cmd= -p $(sudo fuser /var/lib/dpkg/lock-frontend))"; printed=1; fi; echo "Waiting 5 seconds to check again"; sleep 5; done;\'',
"sudo apt-get -y remove nodejs",
"sudo apt-get update",
"sudo apt-get install -y ca-certificates curl gnupg",
"sudo mkdir -p /etc/apt/keyrings",
"sudo rm -f /etc/apt/keyrings/nodesource.gpg",
"curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor --batch -o /etc/apt/keyrings/nodesource.gpg",
"export NODE_MAJOR=18",
"echo \"deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main\" | sudo tee /etc/apt/sources.list.d/nodesource.list",
"sudo apt-get update",
"sudo apt autoremove -y",
"sudo apt-get install nodejs -y",
f"test -n \"{v8_version}\"",
"npm install --prefix $HELIX_WORKITEM_ROOT jsvu -g",
f"$HELIX_WORKITEM_ROOT/bin/jsvu --os=linux64 v8@{v8_version}",
f"export V8_ENGINE_PATH=~/.jsvu/bin/v8-{v8_version}",
"${V8_ENGINE_PATH} -e 'console.log(`V8 version: ${this.version()}`)'"
]
# Add the install_prerequisites to the pre_commands
if os_group == "windows":
# Chain Windows commands with error checking using && to ensure each command succeeds
# If any command fails, set PERF_PREREQS_INSTALL_FAILED and report the error
if install_prerequisites:
combined_prerequisites = " && ".join(install_prerequisites)
helix_pre_commands += [
'echo ** Installing prerequisites **',
f'{combined_prerequisites} || set PERF_PREREQS_INSTALL_FAILED=1',
'if defined PERF_PREREQS_INSTALL_FAILED (echo ** Error: Failed to install prerequisites ** && exit /b 1)'
]
else:
if install_prerequisites:
combined_prequisites = " && ".join(install_prerequisites)
helix_pre_commands += [
'echo "** Installing prerequistes **"',
f"{combined_prequisites} || export PERF_PREREQS_INSTALL_FAILED=1",
'test "x$PERF_PREREQS_INSTALL_FAILED" = "x1" && echo "** Error: Failed to install prerequites **"'
]
# Set MONO_ENV_OPTIONS with for Mono Interpreter runs
if codegen_type.lower() == "interpreter" and runtime_type == "mono":
if os_group == "windows":
helix_pre_commands += ['set MONO_ENV_OPTIONS="--interpreter"']
else:
helix_pre_commands += ['export MONO_ENV_OPTIONS="--interpreter"']
# Enable MSBuild node communication logs
if os_group == "windows":
helix_pre_commands += ["set MSBUILDDEBUGCOMM=1", 'set "MSBUILDDEBUGPATH=%HELIX_WORKITEM_UPLOAD_ROOT%"']
else:
helix_pre_commands += ["export MSBUILDDEBUGCOMM=1", 'export "MSBUILDDEBUGPATH=$HELIX_WORKITEM_UPLOAD_ROOT"']
# Copy the performance repo and root directory to the work item directory
if os_group == "windows":
helix_pre_commands += [
"robocopy /np /nfl /ndl /e %HELIX_CORRELATION_PAYLOAD%\\performance %HELIX_WORKITEM_ROOT%\\performance",
"robocopy /np /nfl /ndl /e %HELIX_CORRELATION_PAYLOAD%\\root %HELIX_WORKITEM_ROOT%" ]
else:
helix_pre_commands += [
"mkdir -p $HELIX_WORKITEM_ROOT/performance && cp -R $HELIX_CORRELATION_PAYLOAD/performance/* $HELIX_WORKITEM_ROOT/performance",
"cp -R $HELIX_CORRELATION_PAYLOAD/root/* $HELIX_WORKITEM_ROOT" ]
# invoke the machine-setup
if os_group == "windows":
helix_pre_commands += ["call %HELIX_WORKITEM_ROOT%\\machine-setup.cmd"]
else:
helix_pre_commands += [
"chmod +x $HELIX_WORKITEM_ROOT/machine-setup.sh",
". $HELIX_WORKITEM_ROOT/machine-setup.sh",
]
# ensure that the PYTHONPATH is set to the scripts directory
# TODO: Run scripts out of work item directory instead of payload directory
if os_group == "windows":
helix_pre_commands += ["set PYTHONPATH=%HELIX_CORRELATION_PAYLOAD%\\scripts%3B%HELIX_CORRELATION_PAYLOAD%"]
else:
helix_pre_commands += ["export PYTHONPATH=$HELIX_CORRELATION_PAYLOAD/scripts:$HELIX_CORRELATION_PAYLOAD"]
if runtime_type == "iOSMono":
if os_group == "windows":
helix_pre_commands += ["%HELIX_CORRELATION_PAYLOAD%\\monoaot\\mono-aot-cross --llvm --version"]
else:
helix_pre_commands += ["$HELIX_CORRELATION_PAYLOAD/monoaot/mono-aot-cross --llvm --version"]
# If we are running not release, make sure we make that very clear.
if build_config.lower() != "release":
banner = [
"(echo ======================================================)",
f"(echo NON-RELEASE BUILD CONFIG: {build_config})",
"(echo ======================================================)",
]
helix_pre_commands = banner + helix_pre_commands
return helix_pre_commands
def get_post_commands(os_group: str, internal: bool, runtime_type: str):
if os_group == "windows":
helix_post_commands = ["set PYTHONPATH=%ORIGPYPATH%"]
else:
helix_post_commands = ["export PYTHONPATH=$ORIGPYPATH"]
if internal:
if os_group == "windows":
# Must use 'call' to invoke deactivate.bat, otherwise the batch file
# transfers control to deactivate.bat and never returns, causing the
# EXIT /b %_commandExitCode% at the end of the Helix work item to never run
helix_post_commands += ["call deactivate"]
else:
helix_post_commands += ["deactivate"] # deactivate venv
if runtime_type == "wasm" and os_group != "windows":
helix_post_commands += [
"""test -d "$HELIX_WORKITEM_UPLOAD_ROOT" && (
export _PERF_DIR=$HELIX_WORKITEM_ROOT/performance;
mkdir -p $HELIX_WORKITEM_UPLOAD_ROOT/log;
find $_PERF_DIR -name '*.binlog' | xargs -I{} cp {} $HELIX_WORKITEM_UPLOAD_ROOT/log;
test "$_commandExitCode" -eq 0 || (
mkdir -p $HELIX_WORKITEM_UPLOAD_ROOT/log/MicroBenchmarks/obj;
mkdir -p $HELIX_WORKITEM_UPLOAD_ROOT/log/MicroBenchmarks/bin;
mkdir -p $HELIX_WORKITEM_UPLOAD_ROOT/log/BenchmarkDotNet.Autogenerated/obj;
mkdir -p $HELIX_WORKITEM_UPLOAD_ROOT/log/for-running;
cp -R $_PERF_DIR/artifacts/obj/MicroBenchmarks $HELIX_WORKITEM_UPLOAD_ROOT/log/MicroBenchmarks/obj;
cp -R $_PERF_DIR/artifacts/bin/MicroBenchmarks $HELIX_WORKITEM_UPLOAD_ROOT/log/MicroBenchmarks/bin;
cp -R $_PERF_DIR/artifacts/obj/BenchmarkDotNet.Autogenerated $HELIX_WORKITEM_UPLOAD_ROOT/log/BenchmarkDotNet.Autogenerated/obj;
cp -R $_PERF_DIR/artifacts/bin/for-running $HELIX_WORKITEM_UPLOAD_ROOT/log/for-running))"""]
return helix_post_commands
def logical_machine_to_queue(logical_machine: str, internal: bool, os_group: str, architecture: str):
if os_group == "windows":
if not internal:
return "Windows.10.Amd64.ClientRS4.DevEx.15.8.Open"
else:
queue_map = {
"perftiger": "Windows.11.Amd64.Tiger.Perf",
"perftiger_crossgen": "Windows.11.Amd64.Tiger.Perf",
"perfpixel4a": "Windows.11.Amd64.Pixel.Perf",
"perfampere": "Windows.Server.Arm64.Perf",
"perfviper": "Windows.11.Amd64.Viper.Perf",
"cloudvm": "Windows.10.Amd64"
}
return queue_map.get(logical_machine, "Windows.11.Amd64.Tiger.Perf")
else:
if not internal:
if architecture == "arm64":
return "ubuntu.1804.armarch.open"
else:
return "Ubuntu.2204.Amd64.Open"
else:
queue_map = {
"perfampere": "Ubuntu.2204.Arm64.Perf",
"perfcobalt": "AzureLinux.3.Cobalt.Arm64.Perf",
"perfiphone17": "Mac.iPhone.17.Perf",
"perftiger_crossgen": "Ubuntu.1804.Amd64.Tiger.Perf",
"perfviper": "Ubuntu.2204.Amd64.Viper.Perf",
"cloudvm": "Ubuntu.2204.Amd64"
}
return queue_map.get(logical_machine, "Ubuntu.2204.Amd64.Tiger.Perf")
def get_bdn_arguments(
run_categories: str,
internal: bool,
os_group: str,
runtime_type: str,
codegen_type: str,
only_sanity_check: bool = False,
affinity: Optional[str] = None,
experiment_name: Optional[str] = None,
javascript_engine: Optional[str] = None,
javascript_engine_path: Optional[str] = None,
product_version: Optional[str] = None,
corerun_payload_dir: Optional[str] = None,
extra_bdn_args: Optional[str] = None) -> list[str]:
bdn_arguments = ["--anyCategories", run_categories]
if affinity is not None and not "0":
bdn_arguments += ["--affinity", affinity]
if not internal:
bdn_arguments += [
"--iterationCount", "1",
"--warmupCount", "0",
"--invocationCount", "1",
"--unrollFactor", "1",
"--strategy", "ColdStart",
"--stopOnFirstError", "true"
]
category_exclusions: list[str] = []
is_aot = codegen_type.lower() == "aot"
if runtime_type == "mono":
# TODO: Validate if this exclusion filter is still needed
bdn_arguments += ["--exclusion-filter", "*Perf_Image*", "*Perf_NamedPipeStream*"]
if is_aot:
category_exclusions += ["NoAOT", "NoWASM"]
bdn_arguments += [
"--runtimes", "monoaotllvm",
"--aotcompilerpath", "$HELIX_CORRELATION_PAYLOAD/monoaot/mono-aot-cross",
"--customruntimepack", "$HELIX_CORRELATION_PAYLOAD/monoaot/pack",
"--aotcompilermode", "llvm",
]
else:
category_exclusions += ["NoMono"]
if codegen_type.lower() == "interpreter":
category_exclusions += ["NoInterpreter"]
if experiment_name == "memoryRandomization":
bdn_arguments += ["--memoryRandomization", "true"]
if runtime_type == "wasm":
category_exclusions += ["NoInterpreter", "NoWASM", "NoMono"]
assert javascript_engine_path is not None
bdn_arguments += [
"--wasmEngine", javascript_engine_path,
"--cli", "$HELIX_CORRELATION_PAYLOAD/dotnet/dotnet",
"--wasmProcessTimeout", "20",
]
# The runtime now uses the standardized exnref WASM exception-handling proposal,
# which V8 keeps behind --experimental-wasm-exnref. Pass it through to the engine
# via BDN's --wasmArgs (the escaped quotes keep it a single token on the Helix shell).
if javascript_engine == "v8":
bdn_arguments += ["\\\"--wasmArgs=--experimental-wasm-exnref\\\""]
if is_aot:
bdn_arguments += [
"--aotcompilermode", "wasm",
"--buildTimeout", "3600"
]
if runtime_type == "wasm_coreclr":
category_exclusions += ["NoWASM", "NoWasmCoreCLR", "NoMono"]
assert javascript_engine_path is not None
bdn_arguments += [
"--wasmEngine", javascript_engine_path,
"--cli", "$HELIX_CORRELATION_PAYLOAD/dotnet/dotnet",
"--buildTimeout", "1200",
"--wasmProcessTimeout", "20"
]
# The runtime now uses the standardized exnref WASM exception-handling proposal,
# which V8 keeps behind --experimental-wasm-exnref. Pass it through to the engine
# via BDN's --wasmArgs (the escaped quotes keep it a single token on the Helix shell).
if javascript_engine == "v8":
bdn_arguments += ["\\\"--wasmArgs=--experimental-wasm-exnref\\\""]
if runtime_type == "coreclr_r2r_interpreter":
if os_group == "windows":
bdn_arguments += [
"--runtimes", "r2r11_0",
"--customruntimepack", "%HELIX_CORRELATION_PAYLOAD%\\r2r_interpreter\\runtimepack",
"--aotcompilerpath", "%HELIX_CORRELATION_PAYLOAD%\\r2r_interpreter\\crossgen2",
]
else:
bdn_arguments += [
"--runtimes", "r2r11_0",
"--customruntimepack", "$HELIX_CORRELATION_PAYLOAD/r2r_interpreter/runtimepack",
"--aotcompilerpath", "$HELIX_CORRELATION_PAYLOAD/r2r_interpreter/crossgen2",
]
if category_exclusions:
bdn_arguments += ["--category-exclusion-filter", *set(category_exclusions)]
bdn_arguments += ["--logBuildOutput", "--generateBinLog"]
if only_sanity_check:
bdn_arguments += ["--filter", "System.Tests.Perf_*"]
if runtime_type == "mono" and not is_aot:
assert product_version is not None
if os_group == "windows":
bdn_arguments += ["--corerun", f"%HELIX_CORRELATION_PAYLOAD%\\dotnet-mono\\shared\\Microsoft.NETCore.App\\{product_version}\\corerun.exe"]
else:
bdn_arguments += ["--corerun", f"$HELIX_CORRELATION_PAYLOAD/dotnet-mono/shared/Microsoft.NETCore.App/{product_version}/corerun"]
if corerun_payload_dir is not None:
if os_group == "windows":
bdn_arguments += ["--corerun", f"%HELIX_CORRELATION_PAYLOAD%\\{corerun_payload_dir}\\CoreRun.exe"]
else:
bdn_arguments += ["--corerun", f"$HELIX_CORRELATION_PAYLOAD/{corerun_payload_dir}/corerun"]
if extra_bdn_args:
bdn_arguments += extra_bdn_args.split(" ")
return bdn_arguments
def get_run_configurations(
run_kind: str,
runtime_type: str,
codegen_type: str,
pgo_run_type: Optional[str] = None,
physical_promotion_run_type: Optional[str] = None,
r2r_run_type: Optional[str] = None,
experiment_name: Optional[str] = None,
linking_type: Optional[str] = None,
runtime_flavor: Optional[str] = None,
ios_llvm_build: bool = False,
ios_strip_symbols: bool = False,
javascript_engine: Optional[str] = None,
build_config: Optional[str] = None):
configurations = { "CompilationMode": "Tiered", "RunKind": run_kind }
is_aot = codegen_type.lower() == "aot"
if runtime_type == "mono":
llvm = is_aot and not run_kind == "android_scenarios"
configurations["LLVM"] = str(llvm)
configurations["MonoInterpreter"] = str(codegen_type.lower() == "interpreter")
configurations["MonoAOT"] = str(is_aot)
if runtime_type == "wasm":
configurations["CompilationMode"] = "wasm"
if is_aot:
configurations["AOT"] = "true"
if javascript_engine == "javascriptcore":
configurations["JSEngine"] = "javascriptcore"
if runtime_type == "wasm_coreclr":
configurations["CompilationMode"] = "wasm"
configurations["RuntimeType"] = str(runtime_flavor)
if is_aot:
configurations["AOT"] = "true"
if pgo_run_type == "nodynamicpgo":
configurations["PGOType"] = "nodynamicpgo"
if physical_promotion_run_type == "physicalpromotion":
configurations["PhysicalPromotionType"] = "physicalpromotion"
if r2r_run_type == "nor2r":
configurations["R2RType"] = "nor2r"
if runtime_type == "coreclr_r2r_interpreter":
configurations["R2RType"] = "r2r_interpreter"
if experiment_name is not None:
configurations["ExperimentName"] = experiment_name
# dotnet/runtime Android sample app scenarios
if run_kind == "android_scenarios":
if not runtime_flavor in ("mono", "coreclr"):
raise Exception("Runtime flavor must be specified for runtime android scenarios")
configurations["CodegenType"] = str(codegen_type)
configurations["LinkingType"] = str(linking_type)
configurations["RuntimeType"] = str(runtime_flavor)
# dotnet/runtime iOS sample app scenarios
if run_kind == "ios_scenarios":
if not runtime_flavor in ("mono", "coreclr"):
raise Exception("Runtime flavor must be specified for runtime ios scenarios")
configurations["CodegenType"] = str(codegen_type)
configurations["RuntimeType"] = str(runtime_flavor)
configurations["iOSStripSymbols"] = str(ios_strip_symbols)
if runtime_flavor == "mono":
configurations["iOSLlvmBuild"] = str(ios_llvm_build)
# .NET Android and .NET MAUI Android sample app scenarios
if run_kind in ["maui_scenarios_android", "maui_scenarios_android_innerloop"]:
if not runtime_flavor in ("mono", "coreclr"):
raise Exception(f"Runtime flavor must be specified for {run_kind}")
configurations["CodegenType"] = str(codegen_type)
configurations["RuntimeType"] = str(runtime_flavor)
if build_config is not None and build_config != DEFAULT_BUILD_CONFIG:
configurations["BuildConfig"] = build_config
# .NET iOS and .NET MAUI iOS sample app scenarios
if run_kind == "maui_scenarios_ios":
if not runtime_flavor in ("mono", "coreclr"):
raise Exception("Runtime flavor must be specified for maui_scenarios_ios")
configurations["CodegenType"] = str(codegen_type)
configurations["RuntimeType"] = str(runtime_flavor)
if build_config is not None and build_config != DEFAULT_BUILD_CONFIG:
configurations["BuildConfig"] = build_config
return configurations
def get_work_item_command(os_group: str, target_csproj: str, architecture: str, perf_lab_framework: str, internal: bool, wasm: bool, bdn_artifacts_dir: str, wasm_coreclr: bool = False, only_sanity_check: bool = False):
if os_group == "windows":
work_item_command = [
"python",
"%HELIX_WORKITEM_ROOT%\\performance\\scripts\\benchmarks_ci.py",
"--csproj", f"%HELIX_WORKITEM_ROOT%\\performance\\{target_csproj}"]
else:
work_item_command = [
"python3",
"$HELIX_WORKITEM_ROOT/performance/scripts/benchmarks_ci.py",
"--csproj", f"$HELIX_WORKITEM_ROOT/performance/{target_csproj}"]
work_item_command += [
"--incremental", "no",
"--architecture", architecture,
"-f", perf_lab_framework]
if internal:
work_item_command += ["--upload-to-perflab-container"]
if perf_lab_framework != "net472":
if os_group == "windows":
work_item_command += ["--dotnet-versions", "%DOTNET_VERSION%"]
else:
work_item_command += ["--dotnet-versions", "$DOTNET_VERSION"]
if wasm:
work_item_command += ["--run-isolated", "--wasm", "--dotnet-path", "$HELIX_CORRELATION_PAYLOAD/dotnet/"]
if wasm_coreclr:
work_item_command += ["--wasm-runtime-flavor", "CoreCLR"]
work_item_command += ["--bdn-artifacts", bdn_artifacts_dir]
return work_item_command
def run_performance_job(args: RunPerformanceJobArgs):
setup_loggers(verbose=True)
if args.queue is None:
if args.logical_machine is None:
raise Exception("Either queue or logical machine must be specifed")
args.queue = logical_machine_to_queue(args.logical_machine, args.internal, args.os_group, args.architecture)
if args.performance_repo_ci:
# needs to be unique to avoid logs overwriting in mc.dot.net
build_config = f"{args.architecture}_{args.channel}_{args.run_kind}"
if args.dotnet_version_link is not None:
build_config = f"{args.architecture}_{args.channel}_Linked_{args.run_kind}"
helix_type = f"test/performance_{build_config}/"
else:
if args.framework is None:
raise Exception("Framework not configured")
build_config = f"{args.architecture}.{args.run_kind}.{args.framework}"
helix_type = f"test/performance/{args.run_kind}/{args.framework}/{args.architecture}/"
if args.runtime_type == "wasm":
if args.codegen_type.lower() == "aot":
helix_type += "/wasm/aot"
else:
helix_type += "/wasm"
if not args.send_to_helix:
# _BuildConfig is used by CI during log publishing
set_environment_variable("_BuildConfig", build_config, save_to_pipeline=True)
if args.project_file is None:
args.project_file = os.path.join(args.performance_repo_dir, "eng", "performance", "helix.proj")
args.performance_repo_dir = os.path.abspath(args.performance_repo_dir)
if args.target_csproj is None:
if args.os_group == "windows":
args.target_csproj="src\\benchmarks\\micro\\MicroBenchmarks.csproj"
else:
args.target_csproj="src/benchmarks/micro/MicroBenchmarks.csproj"
elif args.os_group != "windows":
args.target_csproj = args.target_csproj.replace("\\", "/")
if args.libraries_download_dir is None and not args.performance_repo_ci and args.runtime_repo_dir is not None:
args.libraries_download_dir = os.path.join(args.runtime_repo_dir, "artifacts")
ios_mono = args.runtime_type == "iOSMono"
ios_coreclr = args.runtime_type == "iOSCoreCLR"
ios_nativeaot = args.runtime_type == "iOSNativeAOT"
is_aot = args.codegen_type.lower() == "aot"
is_mono = args.runtime_type == "mono"
mono_aot = is_mono and is_aot
mono_dotnet = is_mono and not is_aot
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
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
payload_dir = os.path.join(working_dir, "payload", "") # Uploaded folder containing everything needed to run the performance test
root_payload_dir = os.path.join(payload_dir, "root") # folder that will get copied into the root of the payload directory
# clear payload directory
if os.path.exists(working_dir):
getLogger().info("Clearing existing payload directory")
shutil.rmtree(working_dir)
# ensure directories exist
os.makedirs(work_item_dir, exist_ok=True)
os.makedirs(root_payload_dir, exist_ok=True)
# Include a copy of the whole performance in the payload directory
performance_payload_dir = os.path.join(payload_dir, "performance")
getLogger().info("Copying performance repository to payload directory")
shutil.copytree(args.performance_repo_dir, performance_payload_dir, ignore=shutil.ignore_patterns("CorrelationStaging", ".git", "artifacts", ".dotnet", ".venv", ".vs"))
# For ML.NET runs, pre-download the SSWE word-embedding model into the payload so the benchmarks
# don't have to fetch it from the network on the (flaky) Helix machines. See
# try_provision_mlnet_resources for details. The matching MICROSOFTML_RESOURCE_PATH env var is
# set in the Helix pre-commands below when this succeeds.
mlnet_resources_provisioned = False
if args.run_kind == "mlnet":
mlnet_resources_provisioned = try_provision_mlnet_resources(payload_dir)
if args.internal:
creator = ""
scenario_arguments = ["--upload-to-perflab-container"]
helix_source_prefix = "official"
if args.helix_access_token is None:
raise Exception("HelixAccessToken environment variable is not configured")
else:
args.helix_access_token = None
os.environ.pop("HelixAccessToken", None) # in case the environment variable is set on the system already
creator = args.build_definition_name or ""
if args.performance_repo_ci:
creator = "dotnet-performance"
scenario_arguments = []
if args.build_reason == "PullRequest":
helix_source_prefix = "pr"
else:
helix_source_prefix = "ci"
if wasm_aot:
build_config = f"wasmaot.{build_config}"
elif wasm:
build_config = f"wasm.{build_config}"
if args.run_kind == "android_scenarios":
if args.runtime_type == "AndroidMono":
args.runtime_flavor = "mono"
elif args.runtime_type == "AndroidCoreCLR":
args.runtime_flavor = "coreclr"
else:
raise Exception("Android scenarios only support Mono and CoreCLR runtimes")
if args.run_kind == "ios_scenarios":
if args.runtime_type == "iOSMono":
args.runtime_flavor = "mono"
elif args.runtime_type == "iOSCoreCLR":
args.runtime_flavor = "coreclr"
elif args.runtime_type == "iOSNativeAOT":
args.runtime_flavor = "coreclr"
else:
raise Exception("iOS scenarios only support Mono and CoreCLR runtimes")
if args.run_kind == "micro" and args.runtime_type == "wasm_coreclr":
if not args.runtime_flavor:
args.runtime_flavor = "coreclr"
branch = os.environ.get("BUILD_SOURCEBRANCH")
cleaned_branch_name = "main"
if branch is not None and branch.startswith("refs/heads/release"):
cleaned_branch_name = branch.replace("refs/heads/", "")
configurations = get_run_configurations(
args.run_kind, args.runtime_type, args.codegen_type, args.pgo_run_type, args.physical_promotion_run_type,
args.r2r_run_type, args.experiment_name, args.linking_type,
args.runtime_flavor, args.ios_llvm_build, args.ios_strip_symbols, args.javascript_engine,
args.build_config
)
ci_setup_arguments = ci_setup.CiSetupArgs(
channel=cleaned_branch_name,
queue=args.queue,
build_configs=[f"{k}={v}" for k, v in configurations.items()],
architecture=args.architecture,
get_perf_hash=True)
ci_setup_arguments.build_number = args.build_number
ci_setup_arguments.only_sanity_check = args.only_sanity_check
# Detect performance repo branch from AzDO resource metadata and append to PERFLAB_BRANCH if non-main
# Set DisableNewBranchLogic=true as a queue-time variable to revert to always uploading as main
disable_branch_logic = os.environ.get("DISABLE_NEW_BRANCH_LOGIC", "").lower() == "true"
if not disable_branch_logic:
perf_repo_ref = os.environ.get("PERF_REPO_BRANCH")
if perf_repo_ref:
perf_branch = perf_repo_ref.replace("refs/heads/", "").replace("refs/tags/", "")
if perf_branch and perf_branch != "main":
ci_setup_arguments.perf_repo_branch = perf_branch
if branch is not None and not (args.performance_repo_ci and branch == "refs/heads/main"):
ci_setup_arguments.branch = branch
if args.perf_repo_hash is not None and not args.performance_repo_ci:
ci_setup_arguments.repository = f"https://github.com/{args.build_repository_name}"
ci_setup_arguments.commit_sha = args.perf_repo_hash
if args.use_local_commit_time:
get_commit_time_command = RunCommand(["git", "show", "-s", "--format=%ci", args.perf_repo_hash], verbose=True)
ci_setup_arguments.commit_time = get_commit_time_command.run_and_get_stdout(args.runtime_repo_dir).strip()
# not_in_lab should stay False for internal dotnet performance CI runs
if not args.internal and not args.performance_repo_ci:
ci_setup_arguments.not_in_lab = True
product_version = None
if mono_dotnet and not mono_aot:
if args.framework is None:
raise Exception("Framework must be specified for Mono dotnet runs")
if args.versions_props_path is None:
if args.runtime_repo_dir is None:
raise Exception("Please provide either the product version, a path to Versions.props, or a runtime repo directory")
args.versions_props_path = os.path.join(args.runtime_repo_dir, "eng", "Versions.props")
with open(args.versions_props_path) as f:
for line in f:
match = re.search(r"ProductVersion>([^<]*)<", line)
if match:
product_version = match.group(1)
break
if product_version is None:
raise Exception("Unable to find ProductVersion in Versions.props")
mono_dotnet_path = os.path.join(payload_dir, "dotnet-mono")
getLogger().info("Copying mono dotnet directory to payload directory")
if args.mono_dotnet_dir is None:
build_mono_payload(
mono_dotnet_path,
args.os_group,
args.framework,
args.build_config,
args.architecture,
product_version,
runtime_repo_dir=args.runtime_repo_dir,
mono_archive_or_dir=os.path.join(args.libraries_download_dir, "bin") if args.libraries_download_dir else None)
else:
shutil.copytree(args.mono_dotnet_dir, mono_dotnet_path, dirs_exist_ok=True)
v8_version = ""
if wasm_coreclr:
if args.libraries_download_dir is None:
raise Exception("Libraries not downloaded for wasm_coreclr runs")
getLogger().info("Building wasm_coreclr payload directory")
browser_wasm_coreclr_dir = os.path.join(args.libraries_download_dir, "BrowserWasmCoreCLR")
build_wasm_coreclr_payload(
browser_wasm_coreclr_dir,
payload_dir,
)
elif wasm:
if args.libraries_download_dir is None:
raise Exception("Libraries not downloaded for wasm runs")
getLogger().info("Copying wasm bundle directory to payload directory")
browser_wasm_dir = os.path.join(args.libraries_download_dir, "BrowserWasm")
build_wasm_payload(
browser_wasm_dir,
payload_dir,
)
if wasm:
if args.javascript_engine == "v8":
if args.browser_versions_props_path is None:
if args.runtime_repo_dir is None:
raise Exception("BrowserVersions.props must be present for wasm runs")
args.browser_versions_props_path = os.path.join(args.runtime_repo_dir, "eng", "testing", "BrowserVersions.props")
with open(args.browser_versions_props_path) as f:
for line in f:
match = re.search(r"linux_V8Version>([^<]*)<", line)
if match:
v8_version = match.group(1)
v8_version = ".".join(v8_version.split(".")[:3])
break
else:
raise Exception("Unable to find v8 version in BrowserVersions.props")
if args.javascript_engine_path is None:
args.javascript_engine_path = f"/home/helixbot/.jsvu/bin/v8-{v8_version}"
if args.javascript_engine_path is None:
args.javascript_engine_path = f"/home/helixbot/.jsvu/bin/{args.javascript_engine}"
ci_setup_arguments.dotnet_path = f"{payload_dir}/dotnet"
if args.dotnet_version_link is not None:
if args.dotnet_version_link.startswith("https"): # Version link is a proper url
if args.dotnet_version_link.endswith(".json"):
with urllib.request.urlopen(args.dotnet_version_link) as response:
values = json.loads(response.read().decode('utf-8'))
if "dotnet_version" in values:
ci_setup_arguments.dotnet_versions = [values["dotnet_version"]]
else:
ci_setup_arguments.dotnet_versions = [values["version"]]
else:
raise ValueError("Invalid dotnet_version_link provided. Must be a json file if a url.")
elif os.path.exists(os.path.join(args.performance_repo_dir, args.dotnet_version_link)) and args.dotnet_version_link.endswith("Version.Details.xml"): # version_link is a file in the perf repo
with open(os.path.join(args.performance_repo_dir, args.dotnet_version_link), encoding="utf-8") as f:
root = ET.fromstring(f.read())
dependency = root.find(".//Dependency[@Name='Microsoft.NET.Sdk']") # For net9.0
if dependency is None: # For older than net9.0
dependency = root.find(".//Dependency[@Name='Microsoft.Dotnet.Sdk.Internal']")
if dependency is not None and "Version" in dependency.attrib: # Get the actual version
ci_setup_arguments.dotnet_versions = [dependency.get("Version", "ERROR: Failed to get version")]
else:
raise ValueError("Unable to find dotnet version in the provided xml file")
else:
raise ValueError("Invalid dotnet_version_link provided")
if args.pgo_run_type == "nodynamicpgo":
ci_setup_arguments.pgo_status = "nodynamicpgo"