2025-10-10T00:06:38.4095108Z Current runner version: '2.328.0' 2025-10-10T00:06:38.4119427Z ##[group]Runner Image Provisioner 2025-10-10T00:06:38.4120234Z Hosted Compute Agent 2025-10-10T00:06:38.4120777Z Version: 20250912.392 2025-10-10T00:06:38.4121963Z Commit: d921fda672a98b64f4f82364647e2f10b2267d0b 2025-10-10T00:06:38.4122693Z Build Date: 2025-09-12T15:23:14Z 2025-10-10T00:06:38.4123291Z ##[endgroup] 2025-10-10T00:06:38.4123831Z ##[group]Operating System 2025-10-10T00:06:38.4124440Z Ubuntu 2025-10-10T00:06:38.4124885Z 24.04.3 2025-10-10T00:06:38.4125378Z LTS 2025-10-10T00:06:38.4125819Z ##[endgroup] 2025-10-10T00:06:38.4126305Z ##[group]Runner Image 2025-10-10T00:06:38.4126804Z Image: ubuntu-24.04 2025-10-10T00:06:38.4127422Z Version: 20250929.60.1 2025-10-10T00:06:38.4128418Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250929.60/images/ubuntu/Ubuntu2404-Readme.md 2025-10-10T00:06:38.4130022Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250929.60 2025-10-10T00:06:38.4131122Z ##[endgroup] 2025-10-10T00:06:38.4132525Z ##[group]GITHUB_TOKEN Permissions 2025-10-10T00:06:38.4134723Z Contents: read 2025-10-10T00:06:38.4135249Z Metadata: read 2025-10-10T00:06:38.4135807Z ##[endgroup] 2025-10-10T00:06:38.4138150Z Secret source: Actions 2025-10-10T00:06:38.4138950Z Prepare workflow directory 2025-10-10T00:06:38.4680173Z Prepare all required actions 2025-10-10T00:06:38.4738344Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (344e6365a0068c2d2847fcec0c55dd53291d475e) 2025-10-10T00:06:38.4743633Z ##[group] Inputs 2025-10-10T00:06:38.4744232Z check_experiments: 2025-10-10T00:06:38.4744906Z opt_out_experiments: 2025-10-10T00:06:38.4745449Z triggering_actor: pytorchmergebot 2025-10-10T00:06:38.4746074Z issue_owner: 2025-10-10T00:06:38.4746620Z curr_branch: main 2025-10-10T00:06:38.4747164Z curr_ref_type: branch 2025-10-10T00:06:38.4747873Z issue_number: 5132 2025-10-10T00:06:38.4748501Z ##[endgroup] 2025-10-10T00:06:38.4749209Z Complete job name: before-test / get-label-type / runner-determinator 2025-10-10T00:06:39.1221250Z ##[group]Run cat < runner_determinator.py 2025-10-10T00:06:39.1223810Z cat < runner_determinator.py 2025-10-10T00:06:39.1224588Z # flake8: noqa: G004 2025-10-10T00:06:39.1225138Z  2025-10-10T00:06:39.1226264Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:06:39.1227382Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:06:39.1228411Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:06:39.1229179Z  2025-10-10T00:06:39.1229672Z """ 2025-10-10T00:06:39.1230451Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:06:39.1231652Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:06:39.1233000Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:06:39.1234021Z of which runners should be used to run which job. 2025-10-10T00:06:39.1234741Z  2025-10-10T00:06:39.1235578Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:06:39.1236647Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:06:39.1237701Z settings are considered to be empty with only the second part, the user 2025-10-10T00:06:39.1238594Z list, defined. 2025-10-10T00:06:39.1239192Z  2025-10-10T00:06:39.1239881Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:06:39.1241194Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:06:39.1242248Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:06:39.1242975Z  2025-10-10T00:06:39.1244104Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:06:39.1245194Z The user list is also a comma separated list of additional features or 2025-10-10T00:06:39.1246100Z experiments which the user could be opted in to. 2025-10-10T00:06:39.1246864Z  2025-10-10T00:06:39.1247375Z The user list has the following rules: 2025-10-10T00:06:39.1248033Z  2025-10-10T00:06:39.1248776Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:06:39.1249853Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:06:39.1250806Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:06:39.1333393Z  2025-10-10T00:06:39.1334117Z Example config: 2025-10-10T00:06:39.1334717Z  # A list of experiments that can be opted into. 2025-10-10T00:06:39.1335515Z  # This defines the behavior they'll induce when opted into. 2025-10-10T00:06:39.1336282Z  # Expected syntax is: 2025-10-10T00:06:39.1337041Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:06:39.1338120Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:06:39.1338963Z  2025-10-10T00:06:39.1339361Z  experiments: 2025-10-10T00:06:39.1339832Z  lf: 2025-10-10T00:06:39.1340283Z  rollout_percent: 25 2025-10-10T00:06:39.1340821Z  all_branches: false 2025-10-10T00:06:39.1341471Z  default: true 2025-10-10T00:06:39.1341950Z  --- 2025-10-10T00:06:39.1342356Z  2025-10-10T00:06:39.1342748Z  # Opt-ins: 2025-10-10T00:06:39.1343437Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:06:39.1344782Z  # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:06:39.1345674Z  # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:06:39.1346422Z  # Experiments should be from the above list. 2025-10-10T00:06:39.1347017Z  2025-10-10T00:06:39.1347431Z  @User1,-lf,split_build 2025-10-10T00:06:39.1347957Z  @User2,lf 2025-10-10T00:06:39.1348420Z  @User3,split_build 2025-10-10T00:06:39.1348914Z """ 2025-10-10T00:06:39.1349316Z  2025-10-10T00:06:39.1349708Z import json 2025-10-10T00:06:39.1350162Z import logging 2025-10-10T00:06:39.1350619Z import os 2025-10-10T00:06:39.1351179Z import random 2025-10-10T00:06:39.1351639Z import re 2025-10-10T00:06:39.1352097Z import sys 2025-10-10T00:06:39.1352587Z from argparse import ArgumentParser 2025-10-10T00:06:39.1353288Z from collections.abc import Iterable 2025-10-10T00:06:39.1353895Z from functools import cache 2025-10-10T00:06:39.1354441Z from logging import LogRecord 2025-10-10T00:06:39.1355028Z from typing import Any, NamedTuple 2025-10-10T00:06:39.1355663Z from urllib.request import Request, urlopen 2025-10-10T00:06:39.1356297Z  2025-10-10T00:06:39.1356688Z import yaml 2025-10-10T00:06:39.1357159Z from github import Auth, Github 2025-10-10T00:06:39.1357745Z from github.Issue import Issue 2025-10-10T00:06:39.1358275Z  2025-10-10T00:06:39.1358662Z  2025-10-10T00:06:39.1359142Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:06:39.1359936Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:06:39.1361030Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:06:39.1361806Z  2025-10-10T00:06:39.1362519Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:06:39.1363179Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:06:39.1363803Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:06:39.1364476Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:06:39.1365054Z  2025-10-10T00:06:39.1365498Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:06:39.1366116Z  2025-10-10T00:06:39.1366540Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:06:39.1367076Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:06:39.1367590Z  2025-10-10T00:06:39.1367966Z  2025-10-10T00:06:39.1368383Z class Experiment(NamedTuple): 2025-10-10T00:06:39.1368943Z  rollout_perc: float = ( 2025-10-10T00:06:39.1369698Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:06:39.1370436Z  ) 2025-10-10T00:06:39.1370873Z  all_branches: bool = ( 2025-10-10T00:06:39.1372008Z  False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:06:39.1372739Z  ) 2025-10-10T00:06:39.1373167Z  default: bool = ( 2025-10-10T00:06:39.1373835Z  True # If True, the experiment is enabled by default for all queries 2025-10-10T00:06:39.1374538Z  ) 2025-10-10T00:06:39.1374934Z  2025-10-10T00:06:39.1375349Z  # Add more fields as needed 2025-10-10T00:06:39.1375879Z  2025-10-10T00:06:39.1376253Z  2025-10-10T00:06:39.1376665Z class Settings(NamedTuple): 2025-10-10T00:06:39.1377186Z  """ 2025-10-10T00:06:39.1377733Z  Settings for the experiments that can be opted into. 2025-10-10T00:06:39.1378366Z  """ 2025-10-10T00:06:39.1378771Z  2025-10-10T00:06:39.1379217Z  experiments: dict[str, Experiment] = {} 2025-10-10T00:06:39.1379802Z  2025-10-10T00:06:39.1380329Z  2025-10-10T00:06:39.1380798Z class ColorFormatter(logging.Formatter): 2025-10-10T00:06:39.1381677Z  """Color codes the log messages based on the log level""" 2025-10-10T00:06:39.1382339Z  2025-10-10T00:06:39.1382739Z  COLORS = { 2025-10-10T00:06:39.1383225Z  "WARNING": "\033[33m", # Yellow 2025-10-10T00:06:39.1383805Z  "ERROR": "\033[31m", # Red 2025-10-10T00:06:39.1384365Z  "CRITICAL": "\033[31m", # Red 2025-10-10T00:06:39.1384948Z  "INFO": "\033[0m", # Reset 2025-10-10T00:06:39.1385517Z  "DEBUG": "\033[0m", # Reset 2025-10-10T00:06:39.1386051Z  } 2025-10-10T00:06:39.1386458Z  2025-10-10T00:06:39.1386935Z  def format(self, record: LogRecord) -> str: 2025-10-10T00:06:39.1387772Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:06:39.1388624Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:06:39.1389267Z  return super().format(record) 2025-10-10T00:06:39.1389807Z  2025-10-10T00:06:39.1390188Z  2025-10-10T00:06:39.1390623Z handler = logging.StreamHandler() 2025-10-10T00:06:39.1391546Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:06:39.1392340Z  2025-10-10T00:06:39.1392851Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:06:39.1393510Z log.addHandler(handler) 2025-10-10T00:06:39.1394037Z log.setLevel(logging.INFO) 2025-10-10T00:06:39.1394550Z  2025-10-10T00:06:39.1394926Z  2025-10-10T00:06:39.1395430Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:06:39.1396064Z  """ 2025-10-10T00:06:39.1396649Z  Defines outputs of the github action that invokes this script 2025-10-10T00:06:39.1397505Z  """ 2025-10-10T00:06:39.1397940Z  if not GITHUB_OUTPUT: 2025-10-10T00:06:39.1399120Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:06:39.1400349Z  log.warning( 2025-10-10T00:06:39.1401520Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:06:39.1402524Z  ) 2025-10-10T00:06:39.1403038Z  print(f"::set-output name={key}::{value}") 2025-10-10T00:06:39.1403657Z  return 2025-10-10T00:06:39.1404101Z  2025-10-10T00:06:39.1404547Z  with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:06:39.1405208Z  log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:06:39.1405846Z  f.write(f"{key}={value}\n") 2025-10-10T00:06:39.1406397Z  2025-10-10T00:06:39.1406779Z  2025-10-10T00:06:39.1407360Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:06:39.1408090Z  return frozenset( 2025-10-10T00:06:39.1408815Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:06:39.1409553Z  ) 2025-10-10T00:06:39.1409968Z  2025-10-10T00:06:39.1410351Z  2025-10-10T00:06:39.1410756Z def parse_args() -> Any: 2025-10-10T00:06:39.1411581Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:06:39.1412558Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:06:39.1413402Z  parser.add_argument( 2025-10-10T00:06:39.1413944Z  "--github-issue-repo", 2025-10-10T00:06:39.1414496Z  type=str, 2025-10-10T00:06:39.1414998Z  required=False, 2025-10-10T00:06:39.1415681Z  default="pytorch/test-infra", 2025-10-10T00:06:39.1416329Z  help="GitHub repo to get the issue", 2025-10-10T00:06:39.1416892Z  ) 2025-10-10T00:06:39.1417331Z  parser.add_argument( 2025-10-10T00:06:39.1417862Z  "--github-repo", 2025-10-10T00:06:39.1418387Z  type=str, 2025-10-10T00:06:39.1418874Z  required=True, 2025-10-10T00:06:39.1419444Z  help="GitHub repo where CI is running", 2025-10-10T00:06:39.1420033Z  ) 2025-10-10T00:06:39.1420462Z  parser.add_argument( 2025-10-10T00:06:39.1421301Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:06:39.1422048Z  ) 2025-10-10T00:06:39.1422490Z  parser.add_argument( 2025-10-10T00:06:39.1423224Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:06:39.1423977Z  ) 2025-10-10T00:06:39.1424411Z  parser.add_argument( 2025-10-10T00:06:39.1425156Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:06:39.1425914Z  ) 2025-10-10T00:06:39.1426342Z  parser.add_argument( 2025-10-10T00:06:39.1427117Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:06:39.1427884Z  ) 2025-10-10T00:06:39.1428357Z  parser.add_argument( 2025-10-10T00:06:39.1428900Z  "--github-ref-type", 2025-10-10T00:06:39.1429438Z  type=str, 2025-10-10T00:06:39.1429935Z  required=True, 2025-10-10T00:06:39.1430548Z  help="Current GitHub ref type, branch or tag", 2025-10-10T00:06:39.1431293Z  ) 2025-10-10T00:06:39.1431737Z  parser.add_argument( 2025-10-10T00:06:39.1432454Z  "--eligible-experiments", 2025-10-10T00:06:39.1433066Z  type=_str_comma_separated_to_set, 2025-10-10T00:06:39.1433660Z  required=False, 2025-10-10T00:06:39.1434169Z  default="", 2025-10-10T00:06:39.1435148Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:06:39.1436139Z  ) 2025-10-10T00:06:39.1436583Z  parser.add_argument( 2025-10-10T00:06:39.1437129Z  "--opt-out-experiments", 2025-10-10T00:06:39.1437733Z  type=_str_comma_separated_to_set, 2025-10-10T00:06:39.1438322Z  required=False, 2025-10-10T00:06:39.1438837Z  default="", 2025-10-10T00:06:39.1439330Z  help=( 2025-10-10T00:06:39.1440099Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:06:39.1441501Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:06:39.1442414Z  ), 2025-10-10T00:06:39.1442843Z  ) 2025-10-10T00:06:39.1443285Z  parser.add_argument( 2025-10-10T00:06:39.1443816Z  "--pr-number", 2025-10-10T00:06:39.1444331Z  type=str, 2025-10-10T00:06:39.1444815Z  required=False, 2025-10-10T00:06:39.1445331Z  default="", 2025-10-10T00:06:39.1445917Z  help="the optional PR number where this is run", 2025-10-10T00:06:39.1446537Z  ) 2025-10-10T00:06:39.1446938Z  2025-10-10T00:06:39.1447377Z  return parser.parse_args() 2025-10-10T00:06:39.1447922Z  2025-10-10T00:06:39.1448301Z  2025-10-10T00:06:39.1448991Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:06:39.1449983Z  auth = Auth.Token(github_token) 2025-10-10T00:06:39.1450633Z  return Github(auth=auth) 2025-10-10T00:06:39.1451639Z  2025-10-10T00:06:39.1452032Z  2025-10-10T00:06:39.1452767Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:06:39.1453668Z  repo = gh.get_repo(repo) 2025-10-10T00:06:39.1454302Z  return repo.get_issue(number=issue_num) 2025-10-10T00:06:39.1454898Z  2025-10-10T00:06:39.1455295Z  2025-10-10T00:06:39.1455720Z def get_potential_pr_author( 2025-10-10T00:06:39.1456482Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:06:39.1457224Z ) -> str: 2025-10-10T00:06:39.1457840Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:06:39.1458754Z  # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:06:39.1459606Z  # embedded in the tag name: ciflow// 2025-10-10T00:06:39.1460249Z  2025-10-10T00:06:39.1460704Z  gh = get_gh_client(github_token) 2025-10-10T00:06:39.1462041Z  2025-10-10T00:06:39.1462611Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:06:39.1463320Z  split_tag = ref_name.split("/") 2025-10-10T00:06:39.1463894Z  if ( 2025-10-10T00:06:39.1464359Z  len(split_tag) == 3 2025-10-10T00:06:39.1464926Z  and split_tag[0] == "ciflow" 2025-10-10T00:06:39.1465517Z  and split_tag[2].isnumeric() 2025-10-10T00:06:39.1466113Z  ): 2025-10-10T00:06:39.1466575Z  pr_number = split_tag[2] 2025-10-10T00:06:39.1467125Z  try: 2025-10-10T00:06:39.1467645Z  repository = gh.get_repo(repo) 2025-10-10T00:06:39.1468592Z  pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:06:39.1469266Z  except Exception as e: 2025-10-10T00:06:39.1469858Z  raise Exception( # noqa: TRY002 2025-10-10T00:06:39.1470601Z  f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:06:39.1471605Z  ) from e 2025-10-10T00:06:39.1472245Z  return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:06:39.1473017Z  # In all other cases, return the original input username 2025-10-10T00:06:39.1473664Z  return username 2025-10-10T00:06:39.1474134Z  2025-10-10T00:06:39.1474506Z  2025-10-10T00:06:39.1474982Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:06:39.1475580Z  """ 2025-10-10T00:06:39.1476318Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:06:39.1477160Z  """ 2025-10-10T00:06:39.1477779Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:06:39.1478500Z  2025-10-10T00:06:39.1478879Z  2025-10-10T00:06:39.1479314Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:06:39.1479859Z  try: 2025-10-10T00:06:39.1480316Z  data = yaml.safe_load(yaml_text) 2025-10-10T00:06:39.1480875Z  return data 2025-10-10T00:06:39.1481664Z  except yaml.YAMLError: 2025-10-10T00:06:39.1482275Z  log.exception("Error loading YAML") 2025-10-10T00:06:39.1482893Z  raise 2025-10-10T00:06:39.1483378Z  2025-10-10T00:06:39.1483797Z  2025-10-10T00:06:39.1484523Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:06:39.1485316Z  """ 2025-10-10T00:06:39.1486181Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:06:39.1487007Z  2025-10-10T00:06:39.1487611Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:39.1488476Z  and the text below is the list of opted in users. 2025-10-10T00:06:39.1489084Z  2025-10-10T00:06:39.1489718Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:06:39.1490463Z  """ 2025-10-10T00:06:39.1491160Z  rollout_state_parts = rollout_state.split("---") 2025-10-10T00:06:39.1491836Z  if len(rollout_state_parts) >= 2: 2025-10-10T00:06:39.1492516Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:06:39.1493364Z  else: 2025-10-10T00:06:39.1493962Z  return "", rollout_state 2025-10-10T00:06:39.1494518Z  2025-10-10T00:06:39.1494888Z  2025-10-10T00:06:39.1495340Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:06:39.1495891Z  """ 2025-10-10T00:06:39.1496484Z  Dictionary of users with a list of features they have opted into 2025-10-10T00:06:39.1497181Z  """ 2025-10-10T00:06:39.1497582Z  2025-10-10T00:06:39.1497952Z  2025-10-10T00:06:39.1498528Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:06:39.1499234Z  """ 2025-10-10T00:06:39.1500022Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:06:39.1501165Z  2025-10-10T00:06:39.1502059Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:06:39.1503111Z  - Example line: "@User1,lf,split_build" 2025-10-10T00:06:39.1504112Z  - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:06:39.1504787Z  2025-10-10T00:06:39.1505160Z  2025-10-10T00:06:39.1505516Z  """ 2025-10-10T00:06:39.1505945Z  optins = UserOptins() 2025-10-10T00:06:39.1506512Z  for user in user_optin_text.split("\n"): 2025-10-10T00:06:39.1507134Z  user = user.strip("\r\n\t -") 2025-10-10T00:06:39.1507757Z  if not user or not user.startswith("@"): 2025-10-10T00:06:39.1508368Z  # Not a valid user. Skip 2025-10-10T00:06:39.1508914Z  continue 2025-10-10T00:06:39.1509368Z  2025-10-10T00:06:39.1509791Z  if user: 2025-10-10T00:06:39.1510302Z  usr_name = user.split(",")[0].strip("@") 2025-10-10T00:06:39.1511246Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:06:39.1511955Z  2025-10-10T00:06:39.1512353Z  return optins 2025-10-10T00:06:39.1512814Z  2025-10-10T00:06:39.1513180Z  2025-10-10T00:06:39.1513727Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:06:39.1514388Z  """ 2025-10-10T00:06:39.1514864Z  Check if the experiment name is valid. 2025-10-10T00:06:39.1515446Z  A valid name: 2025-10-10T00:06:39.1516169Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:06:39.1517175Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:06:39.1517944Z  - Cannot contain spaces 2025-10-10T00:06:39.1518476Z  """ 2025-10-10T00:06:39.1518872Z  2025-10-10T00:06:39.1519385Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:06:39.1520186Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:06:39.1521186Z  2025-10-10T00:06:39.1521659Z  if valid: 2025-10-10T00:06:39.1522136Z  return True 2025-10-10T00:06:39.1522596Z  2025-10-10T00:06:39.1522994Z  log.error( 2025-10-10T00:06:39.1524548Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-10-10T00:06:39.1526184Z  ) 2025-10-10T00:06:39.1526608Z  return False 2025-10-10T00:06:39.1527071Z  2025-10-10T00:06:39.1527439Z  2025-10-10T00:06:39.1528008Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:06:39.1528704Z  """ 2025-10-10T00:06:39.1529381Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:06:39.1530176Z  """ 2025-10-10T00:06:39.1530589Z  try: 2025-10-10T00:06:39.1531218Z  if settings_text: 2025-10-10T00:06:39.1532053Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:06:39.1532947Z  # for easy reading 2025-10-10T00:06:39.1533837Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:06:39.1534824Z  # the backtick character in shell commands. 2025-10-10T00:06:39.1535513Z  backtick = chr(96) # backtick character 2025-10-10T00:06:39.1536284Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:06:39.1537045Z  settings = load_yaml(settings_text) 2025-10-10T00:06:39.1537610Z  2025-10-10T00:06:39.1538298Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:06:39.1539426Z  experiments = {} 2025-10-10T00:06:39.1539945Z  2025-10-10T00:06:39.1540564Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:06:39.1541728Z  if not is_valid_experiment_name(exp_name): 2025-10-10T00:06:39.1542931Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-10-10T00:06:39.1544080Z  continue 2025-10-10T00:06:39.1544610Z  2025-10-10T00:06:39.1545048Z  valid_settings = {} 2025-10-10T00:06:39.1545638Z  for setting in exp_settings: 2025-10-10T00:06:39.1546274Z  if setting not in Experiment._fields: 2025-10-10T00:06:39.1546914Z  log.warning( 2025-10-10T00:06:39.1547723Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:06:39.1548498Z  ) 2025-10-10T00:06:39.1549001Z  else: 2025-10-10T00:06:39.1549617Z  valid_settings[setting] = exp_settings[setting] 2025-10-10T00:06:39.1550246Z  2025-10-10T00:06:39.1550789Z  experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:06:39.1551757Z  return Settings(experiments) 2025-10-10T00:06:39.1552311Z  2025-10-10T00:06:39.1552719Z  except Exception: 2025-10-10T00:06:39.1553303Z  log.exception("Failed to parse settings") 2025-10-10T00:06:39.1553913Z  2025-10-10T00:06:39.1554311Z  return Settings() 2025-10-10T00:06:39.1554798Z  2025-10-10T00:06:39.1555188Z  2025-10-10T00:06:39.1555880Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:06:39.1556524Z  """ 2025-10-10T00:06:39.1557051Z  Parse settings, if any, from the rollout state. 2025-10-10T00:06:39.1557651Z  2025-10-10T00:06:39.1558255Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:39.1559097Z  and the text below is the list of opted in users. 2025-10-10T00:06:39.1559699Z  2025-10-10T00:06:39.1560356Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:06:39.1561249Z  """ 2025-10-10T00:06:39.1561886Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:39.1562722Z  return parse_settings_from_text(settings_text) 2025-10-10T00:06:39.1563309Z  2025-10-10T00:06:39.1563681Z  2025-10-10T00:06:39.1564193Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:06:39.1564814Z  """ 2025-10-10T00:06:39.1565276Z  Parse users from the rollout state. 2025-10-10T00:06:39.1565865Z  2025-10-10T00:06:39.1566235Z  """ 2025-10-10T00:06:39.1566845Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:39.1567664Z  return parse_user_opt_in_from_text(users_text) 2025-10-10T00:06:39.1568252Z  2025-10-10T00:06:39.1568624Z  2025-10-10T00:06:39.1569301Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:39.1570106Z  """ 2025-10-10T00:06:39.1570590Z  Check if a user is opted into an experiment 2025-10-10T00:06:39.1571274Z  """ 2025-10-10T00:06:39.1571808Z  return experiment_name in user_optins.get(user, []) 2025-10-10T00:06:39.1572438Z  2025-10-10T00:06:39.1572956Z  2025-10-10T00:06:39.1573637Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:39.1574457Z  """ 2025-10-10T00:06:39.1574992Z  Check if a user explicitly opted out of an experiment 2025-10-10T00:06:39.1575620Z  """ 2025-10-10T00:06:39.1576194Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:06:39.1576967Z  experiment_optout = "-" + experiment_name 2025-10-10T00:06:39.1577687Z  if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:06:39.1578345Z  return False 2025-10-10T00:06:39.1578818Z  2025-10-10T00:06:39.1579326Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:06:39.1579981Z  log.warning( 2025-10-10T00:06:39.1581015Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:06:39.1581962Z  ) 2025-10-10T00:06:39.1582375Z  2025-10-10T00:06:39.1582761Z  return True 2025-10-10T00:06:39.1583225Z  2025-10-10T00:06:39.1583599Z  2025-10-10T00:06:39.1584006Z def get_runner_prefix( 2025-10-10T00:06:39.1584519Z  rollout_state: str, 2025-10-10T00:06:39.1585075Z  workflow_requestors: Iterable[str], 2025-10-10T00:06:39.1585643Z  branch: str, 2025-10-10T00:06:39.1586235Z  eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:39.1586984Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:39.1587624Z  is_canary: bool = False, 2025-10-10T00:06:39.1588145Z ) -> str: 2025-10-10T00:06:39.1588650Z  settings = parse_settings(rollout_state) 2025-10-10T00:06:39.1589312Z  user_optins = parse_users(rollout_state) 2025-10-10T00:06:39.1589888Z  2025-10-10T00:06:39.1590420Z  fleet_prefix = "" 2025-10-10T00:06:39.1591043Z  prefixes = [] 2025-10-10T00:06:39.1591776Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:06:39.1592805Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:06:39.1593572Z  log.info( 2025-10-10T00:06:39.1594341Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:06:39.1595144Z  ) 2025-10-10T00:06:39.1595629Z  continue 2025-10-10T00:06:39.1596100Z  2025-10-10T00:06:39.1596513Z  if opt_out_experiments: 2025-10-10T00:06:39.1597143Z  if experiment_name in opt_out_experiments: 2025-10-10T00:06:39.1597866Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:06:39.1598530Z  log.info( 2025-10-10T00:06:39.1599548Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:06:39.1600656Z  ) 2025-10-10T00:06:39.1601241Z  continue 2025-10-10T00:06:39.1601729Z  2025-10-10T00:06:39.1602153Z  if eligible_experiments: 2025-10-10T00:06:39.1602793Z  if experiment_name not in eligible_experiments: 2025-10-10T00:06:39.1603500Z  exp_list = ", ".join(eligible_experiments) 2025-10-10T00:06:39.1604111Z  log.info( 2025-10-10T00:06:39.1604986Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:06:39.1605879Z  ) 2025-10-10T00:06:39.1606504Z  continue 2025-10-10T00:06:39.1607091Z  elif not experiment_settings.default: 2025-10-10T00:06:39.1607679Z  log.info( 2025-10-10T00:06:39.1608435Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:06:39.1609227Z  ) 2025-10-10T00:06:39.1609681Z  continue 2025-10-10T00:06:39.1610150Z  2025-10-10T00:06:39.1610674Z  # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:06:39.1611469Z  opted_out_users = [ 2025-10-10T00:06:39.1611998Z  requestor 2025-10-10T00:06:39.1612548Z  for requestor in workflow_requestors 2025-10-10T00:06:39.1613298Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:06:39.1613980Z  ] 2025-10-10T00:06:39.1614401Z  2025-10-10T00:06:39.1614811Z  if opted_out_users: 2025-10-10T00:06:39.1615363Z  log.info( 2025-10-10T00:06:39.1616076Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:06:39.1616830Z  ) 2025-10-10T00:06:39.1617283Z  continue 2025-10-10T00:06:39.1617757Z  2025-10-10T00:06:39.1618278Z  # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:06:39.1618956Z  opted_in_users = [ 2025-10-10T00:06:39.1619530Z  requestor 2025-10-10T00:06:39.1620100Z  for requestor in workflow_requestors 2025-10-10T00:06:39.1620854Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:06:39.1621644Z  ] 2025-10-10T00:06:39.1622066Z  2025-10-10T00:06:39.1622471Z  enabled = False 2025-10-10T00:06:39.1622994Z  if opted_in_users: 2025-10-10T00:06:39.1623640Z  log.info( 2025-10-10T00:06:39.1624366Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:06:39.1625108Z  ) 2025-10-10T00:06:39.1625560Z  enabled = True 2025-10-10T00:06:39.1626067Z  2025-10-10T00:06:39.1626520Z  elif experiment_settings.rollout_perc: 2025-10-10T00:06:39.1627439Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:06:39.1628482Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:06:39.1629212Z  log.info( 2025-10-10T00:06:39.1630186Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:06:39.1631278Z  ) 2025-10-10T00:06:39.1631792Z  enabled = True 2025-10-10T00:06:39.1632315Z  2025-10-10T00:06:39.1632710Z  if enabled: 2025-10-10T00:06:39.1633224Z  label = experiment_name 2025-10-10T00:06:39.1633853Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:06:39.1634766Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:06:39.1635731Z  # - If it's enabled, then we always list it's prefix first 2025-10-10T00:06:39.1636582Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:06:39.1637310Z  if is_canary: 2025-10-10T00:06:39.1637886Z  label += CANARY_FLEET_SUFFIX 2025-10-10T00:06:39.1638498Z  fleet_prefix = label 2025-10-10T00:06:39.1639052Z  else: 2025-10-10T00:06:39.1639706Z  prefixes.append(label) 2025-10-10T00:06:39.1640268Z  2025-10-10T00:06:39.1640671Z  if len(prefixes) > 1: 2025-10-10T00:06:39.1642100Z  log.error( 2025-10-10T00:06:39.1644244Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-10-10T00:06:39.1646404Z  ) 2025-10-10T00:06:39.1647206Z  prefixes = prefixes[:1] 2025-10-10T00:06:39.1648154Z  2025-10-10T00:06:39.1648866Z  # Fleet always comes first 2025-10-10T00:06:39.1649838Z  if fleet_prefix: 2025-10-10T00:06:39.1650784Z  prefixes.insert(0, fleet_prefix) 2025-10-10T00:06:39.1652011Z  2025-10-10T00:06:39.1652885Z  return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:06:39.1654019Z  2025-10-10T00:06:39.1654749Z  2025-10-10T00:06:39.1656014Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:06:39.1657592Z  """ 2025-10-10T00:06:39.1658770Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:06:39.1660216Z  2025-10-10T00:06:39.1661550Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:06:39.1662959Z  """ 2025-10-10T00:06:39.1663780Z  gh = get_gh_client(github_token) 2025-10-10T00:06:39.1664880Z  issue = get_issue(gh, repo, issue_num) 2025-10-10T00:06:39.1666232Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:06:39.1667464Z  2025-10-10T00:06:39.1668127Z  2025-10-10T00:06:39.1669328Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:06:39.1671230Z  for _ in range(num_retries): 2025-10-10T00:06:39.1672277Z  try: 2025-10-10T00:06:39.1673148Z  req = Request(url=url, headers=headers) 2025-10-10T00:06:39.1674429Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:06:39.1675692Z  return json.loads(content) 2025-10-10T00:06:39.1676715Z  except Exception as e: 2025-10-10T00:06:39.1677848Z  log.warning(f"Could not download {url}: {e}") 2025-10-10T00:06:39.1678927Z  2025-10-10T00:06:39.1680063Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:06:39.1681626Z  return {} 2025-10-10T00:06:39.1682408Z  2025-10-10T00:06:39.1683040Z  2025-10-10T00:06:39.1683704Z @cache 2025-10-10T00:06:39.1684971Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:06:39.1686471Z  """ 2025-10-10T00:06:39.1687300Z  Dynamically get PR information 2025-10-10T00:06:39.1688270Z  """ 2025-10-10T00:06:39.1689295Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:06:39.1690531Z  headers = { 2025-10-10T00:06:39.1691672Z  "Accept": "application/vnd.github.v3+json", 2025-10-10T00:06:39.1692869Z  "Authorization": f"token {github_token}", 2025-10-10T00:06:39.1693901Z  } 2025-10-10T00:06:39.1694818Z  json_response: dict[str, Any] = download_json( 2025-10-10T00:06:39.1695989Z  url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:06:39.1697067Z  headers=headers, 2025-10-10T00:06:39.1697929Z  ) 2025-10-10T00:06:39.1698620Z  2025-10-10T00:06:39.1699317Z  if not json_response: 2025-10-10T00:06:39.1700474Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:06:39.1702204Z  return {} 2025-10-10T00:06:39.1703025Z  2025-10-10T00:06:39.1703734Z  return json_response 2025-10-10T00:06:39.1704610Z  2025-10-10T00:06:39.1705276Z  2025-10-10T00:06:39.1706425Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:06:39.1707865Z  """ 2025-10-10T00:06:39.1708935Z  Dynamically get the latest list of labels from the pull request 2025-10-10T00:06:39.1710181Z  """ 2025-10-10T00:06:39.1711304Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:06:39.1712494Z  return { 2025-10-10T00:06:39.1713647Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:06:39.1714960Z  } 2025-10-10T00:06:39.1715654Z  2025-10-10T00:06:39.1716315Z  2025-10-10T00:06:39.1717000Z def main() -> None: 2025-10-10T00:06:39.1717861Z  args = parse_args() 2025-10-10T00:06:39.1718703Z  2025-10-10T00:06:39.1719507Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:06:39.1720522Z  2025-10-10T00:06:39.1721502Z  # Check if the PR is opt-out 2025-10-10T00:06:39.1722495Z  if args.pr_number: 2025-10-10T00:06:39.1723852Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:06:39.1725327Z  if OPT_OUT_LABEL in labels: 2025-10-10T00:06:39.1726291Z  log.info( 2025-10-10T00:06:39.1727680Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:06:39.1729134Z  ) 2025-10-10T00:06:39.1730275Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:39.1731783Z  sys.exit() 2025-10-10T00:06:39.1732863Z  2025-10-10T00:06:39.1733550Z  try: 2025-10-10T00:06:39.1734437Z  rollout_state = get_rollout_state_from_issue( 2025-10-10T00:06:39.1735872Z  args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:06:39.1737101Z  ) 2025-10-10T00:06:39.1737841Z  2025-10-10T00:06:39.1738600Z  username = get_potential_pr_author( 2025-10-10T00:06:39.1739670Z  args.github_token, 2025-10-10T00:06:39.1740633Z  args.github_repo, 2025-10-10T00:06:39.1741787Z  args.github_actor, 2025-10-10T00:06:39.1742775Z  args.github_ref_type, 2025-10-10T00:06:39.1743756Z  args.github_branch, 2025-10-10T00:06:39.1744684Z  ) 2025-10-10T00:06:39.1745406Z  2025-10-10T00:06:39.1746360Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:06:39.1747531Z  2025-10-10T00:06:39.1748334Z  runner_label_prefix = get_runner_prefix( 2025-10-10T00:06:39.1749400Z  rollout_state, 2025-10-10T00:06:39.1750399Z  (args.github_issue_owner, username), 2025-10-10T00:06:39.1751629Z  args.github_branch, 2025-10-10T00:06:39.1752643Z  args.eligible_experiments, 2025-10-10T00:06:39.1753718Z  args.opt_out_experiments, 2025-10-10T00:06:39.1754724Z  is_canary, 2025-10-10T00:06:39.1755580Z  ) 2025-10-10T00:06:39.1756279Z  2025-10-10T00:06:39.1757020Z  except Exception as e: 2025-10-10T00:06:39.1757923Z  log.error( 2025-10-10T00:06:39.1759281Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:06:39.1761175Z  ) 2025-10-10T00:06:39.1761920Z  2025-10-10T00:06:39.1762931Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:39.1764176Z  2025-10-10T00:06:39.1764830Z  2025-10-10T00:06:39.1765508Z if __name__ == "__main__": 2025-10-10T00:06:39.1766478Z  main() 2025-10-10T00:06:39.1767217Z  2025-10-10T00:06:39.1767859Z EOF 2025-10-10T00:06:39.1768543Z  2025-10-10T00:06:39.1769249Z cat runner_determinator.py 2025-10-10T00:06:39.5652952Z shell: /usr/bin/bash -e {0} 2025-10-10T00:06:39.5654076Z env: 2025-10-10T00:06:39.5654919Z GITHUB_TOKEN: *** 2025-10-10T00:06:39.5655370Z ISSUE_NUMBER: 5132 2025-10-10T00:06:39.5655854Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:06:39.5656389Z ISSUE_OWNER: 2025-10-10T00:06:39.5656818Z CHECK_EXPERIMENTS: 2025-10-10T00:06:39.5657271Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:06:39.5657730Z PR_NUMBER: 2025-10-10T00:06:39.5658182Z ##[endgroup] 2025-10-10T00:06:39.5879996Z # flake8: noqa: G004 2025-10-10T00:06:39.5880411Z 2025-10-10T00:06:39.5880863Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:06:39.5882163Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:06:39.5882992Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:06:39.5883434Z 2025-10-10T00:06:39.5883599Z """ 2025-10-10T00:06:39.5884183Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:06:39.5885072Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:06:39.5885977Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:06:39.5886867Z of which runners should be used to run which job. 2025-10-10T00:06:39.5887265Z 2025-10-10T00:06:39.5887654Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:06:39.5888842Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:06:39.5889777Z settings are considered to be empty with only the second part, the user 2025-10-10T00:06:39.5890479Z list, defined. 2025-10-10T00:06:39.5890720Z 2025-10-10T00:06:39.5891386Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:06:39.5892352Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:06:39.5893202Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:06:39.5893647Z 2025-10-10T00:06:39.5894027Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:06:39.5894901Z The user list is also a comma separated list of additional features or 2025-10-10T00:06:39.5895654Z experiments which the user could be opted in to. 2025-10-10T00:06:39.5896071Z 2025-10-10T00:06:39.5896272Z The user list has the following rules: 2025-10-10T00:06:39.5896636Z 2025-10-10T00:06:39.5896976Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:06:39.5897875Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:06:39.5898647Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:06:39.5899050Z 2025-10-10T00:06:39.5899229Z Example config: 2025-10-10T00:06:39.5899695Z # A list of experiments that can be opted into. 2025-10-10T00:06:39.5900371Z # This defines the behavior they'll induce when opted into. 2025-10-10T00:06:39.5901125Z # Expected syntax is: 2025-10-10T00:06:39.5901794Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:06:39.5902771Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:06:39.5903405Z 2025-10-10T00:06:39.5903577Z experiments: 2025-10-10T00:06:39.5903990Z lf: 2025-10-10T00:06:39.5904383Z rollout_percent: 25 2025-10-10T00:06:39.5905053Z all_branches: false 2025-10-10T00:06:39.5905523Z default: true 2025-10-10T00:06:39.5905945Z --- 2025-10-10T00:06:39.5906160Z 2025-10-10T00:06:39.5906325Z # Opt-ins: 2025-10-10T00:06:39.5906927Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:06:39.5907804Z # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:06:39.5908599Z # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:06:39.5909267Z # Experiments should be from the above list. 2025-10-10T00:06:39.5909654Z 2025-10-10T00:06:39.5909838Z @User1,-lf,split_build 2025-10-10T00:06:39.5910285Z @User2,lf 2025-10-10T00:06:39.5910682Z @User3,split_build 2025-10-10T00:06:39.5911365Z """ 2025-10-10T00:06:39.5911594Z 2025-10-10T00:06:39.5911788Z import json 2025-10-10T00:06:39.5912223Z import logging 2025-10-10T00:06:39.5912657Z import os 2025-10-10T00:06:39.5913081Z import random 2025-10-10T00:06:39.5913520Z import re 2025-10-10T00:06:39.5913953Z import sys 2025-10-10T00:06:39.5914409Z from argparse import ArgumentParser 2025-10-10T00:06:39.5914950Z from collections.abc import Iterable 2025-10-10T00:06:39.5915494Z from functools import cache 2025-10-10T00:06:39.5915984Z from logging import LogRecord 2025-10-10T00:06:39.5916486Z from typing import Any, NamedTuple 2025-10-10T00:06:39.5917027Z from urllib.request import Request, urlopen 2025-10-10T00:06:39.5917409Z 2025-10-10T00:06:39.5917576Z import yaml 2025-10-10T00:06:39.5917976Z from github import Auth, Github 2025-10-10T00:06:39.5918470Z from github.Issue import Issue 2025-10-10T00:06:39.5918779Z 2025-10-10T00:06:39.5918786Z 2025-10-10T00:06:39.5919015Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:06:39.5919703Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:06:39.5920573Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:06:39.5921248Z 2025-10-10T00:06:39.5921488Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:06:39.5922224Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:06:39.5922748Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:06:39.5923305Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:06:39.5923659Z 2025-10-10T00:06:39.5923861Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:06:39.5924214Z 2025-10-10T00:06:39.5924399Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:06:39.5924875Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:06:39.5925158Z 2025-10-10T00:06:39.5925165Z 2025-10-10T00:06:39.5925352Z class Experiment(NamedTuple): 2025-10-10T00:06:39.5925838Z rollout_perc: float = ( 2025-10-10T00:06:39.5926483Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:06:39.5927169Z ) 2025-10-10T00:06:39.5927547Z all_branches: bool = ( 2025-10-10T00:06:39.5928183Z False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:06:39.5928862Z ) 2025-10-10T00:06:39.5929239Z default: bool = ( 2025-10-10T00:06:39.5929832Z True # If True, the experiment is enabled by default for all queries 2025-10-10T00:06:39.5930478Z ) 2025-10-10T00:06:39.5930683Z 2025-10-10T00:06:39.5930863Z # Add more fields as needed 2025-10-10T00:06:39.5931283Z 2025-10-10T00:06:39.5931289Z 2025-10-10T00:06:39.5931484Z class Settings(NamedTuple): 2025-10-10T00:06:39.5931934Z """ 2025-10-10T00:06:39.5932399Z Settings for the experiments that can be opted into. 2025-10-10T00:06:39.5932979Z """ 2025-10-10T00:06:39.5933181Z 2025-10-10T00:06:39.5933399Z experiments: dict[str, Experiment] = {} 2025-10-10T00:06:39.5933760Z 2025-10-10T00:06:39.5933768Z 2025-10-10T00:06:39.5933981Z class ColorFormatter(logging.Formatter): 2025-10-10T00:06:39.5934615Z """Color codes the log messages based on the log level""" 2025-10-10T00:06:39.5935052Z 2025-10-10T00:06:39.5935223Z COLORS = { 2025-10-10T00:06:39.5935624Z "WARNING": "\033[33m", # Yellow 2025-10-10T00:06:39.5936289Z "ERROR": "\033[31m", # Red 2025-10-10T00:06:39.5936792Z "CRITICAL": "\033[31m", # Red 2025-10-10T00:06:39.5937300Z "INFO": "\033[0m", # Reset 2025-10-10T00:06:39.5937788Z "DEBUG": "\033[0m", # Reset 2025-10-10T00:06:39.5938261Z } 2025-10-10T00:06:39.5938461Z 2025-10-10T00:06:39.5938687Z def format(self, record: LogRecord) -> str: 2025-10-10T00:06:39.5939448Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:06:39.5940232Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:06:39.5940809Z return super().format(record) 2025-10-10T00:06:39.5941262Z 2025-10-10T00:06:39.5941270Z 2025-10-10T00:06:39.5941475Z handler = logging.StreamHandler() 2025-10-10T00:06:39.5942181Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:06:39.5942738Z 2025-10-10T00:06:39.5942984Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:06:39.5943571Z log.addHandler(handler) 2025-10-10T00:06:39.5944028Z log.setLevel(logging.INFO) 2025-10-10T00:06:39.5944319Z 2025-10-10T00:06:39.5944326Z 2025-10-10T00:06:39.5944609Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:06:39.5945205Z """ 2025-10-10T00:06:39.5945730Z Defines outputs of the github action that invokes this script 2025-10-10T00:06:39.5946362Z """ 2025-10-10T00:06:39.5946742Z if not GITHUB_OUTPUT: 2025-10-10T00:06:39.5947824Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:06:39.5948953Z log.warning( 2025-10-10T00:06:39.5949814Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:06:39.5950745Z ) 2025-10-10T00:06:39.6020535Z print(f"::set-output name={key}::{value}") 2025-10-10T00:06:39.6021846Z return 2025-10-10T00:06:39.6022327Z 2025-10-10T00:06:39.6023041Z with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:06:39.6024068Z log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:06:39.6025077Z f.write(f"{key}={value}\n") 2025-10-10T00:06:39.6025652Z 2025-10-10T00:06:39.6025663Z 2025-10-10T00:06:39.6026196Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:06:39.6027328Z return frozenset( 2025-10-10T00:06:39.6028410Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:06:39.6029633Z ) 2025-10-10T00:06:39.6029978Z 2025-10-10T00:06:39.6029989Z 2025-10-10T00:06:39.6030304Z def parse_args() -> Any: 2025-10-10T00:06:39.6031488Z parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:06:39.6033052Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:06:39.6034443Z parser.add_argument( 2025-10-10T00:06:39.6035238Z "--github-issue-repo", 2025-10-10T00:06:39.6036062Z type=str, 2025-10-10T00:06:39.6036754Z required=False, 2025-10-10T00:06:39.6037558Z default="pytorch/test-infra", 2025-10-10T00:06:39.6038469Z help="GitHub repo to get the issue", 2025-10-10T00:06:39.6039004Z ) 2025-10-10T00:06:39.6039382Z parser.add_argument( 2025-10-10T00:06:39.6039841Z "--github-repo", 2025-10-10T00:06:39.6040278Z type=str, 2025-10-10T00:06:39.6040679Z required=True, 2025-10-10T00:06:39.6041450Z help="GitHub repo where CI is running", 2025-10-10T00:06:39.6041990Z ) 2025-10-10T00:06:39.6042374Z parser.add_argument( 2025-10-10T00:06:39.6042982Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:06:39.6043649Z ) 2025-10-10T00:06:39.6044021Z parser.add_argument( 2025-10-10T00:06:39.6044651Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:06:39.6045332Z ) 2025-10-10T00:06:39.6045706Z parser.add_argument( 2025-10-10T00:06:39.6046604Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:06:39.6047302Z ) 2025-10-10T00:06:39.6047681Z parser.add_argument( 2025-10-10T00:06:39.6048336Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:06:39.6049065Z ) 2025-10-10T00:06:39.6049428Z parser.add_argument( 2025-10-10T00:06:39.6049887Z "--github-ref-type", 2025-10-10T00:06:39.6050339Z type=str, 2025-10-10T00:06:39.6050742Z required=True, 2025-10-10T00:06:39.6051498Z help="Current GitHub ref type, branch or tag", 2025-10-10T00:06:39.6052066Z ) 2025-10-10T00:06:39.6052441Z parser.add_argument( 2025-10-10T00:06:39.6052901Z "--eligible-experiments", 2025-10-10T00:06:39.6053424Z type=_str_comma_separated_to_set, 2025-10-10T00:06:39.6053943Z required=False, 2025-10-10T00:06:39.6054369Z default="", 2025-10-10T00:06:39.6055227Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:06:39.6056167Z ) 2025-10-10T00:06:39.6056542Z parser.add_argument( 2025-10-10T00:06:39.6057009Z "--opt-out-experiments", 2025-10-10T00:06:39.6057520Z type=_str_comma_separated_to_set, 2025-10-10T00:06:39.6058048Z required=False, 2025-10-10T00:06:39.6058471Z default="", 2025-10-10T00:06:39.6058861Z help=( 2025-10-10T00:06:39.6059534Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:06:39.6060677Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:06:39.6061639Z ), 2025-10-10T00:06:39.6061998Z ) 2025-10-10T00:06:39.6062374Z parser.add_argument( 2025-10-10T00:06:39.6062809Z "--pr-number", 2025-10-10T00:06:39.6063228Z type=str, 2025-10-10T00:06:39.6063631Z required=False, 2025-10-10T00:06:39.6064074Z default="", 2025-10-10T00:06:39.6064695Z help="the optional PR number where this is run", 2025-10-10T00:06:39.6065265Z ) 2025-10-10T00:06:39.6065466Z 2025-10-10T00:06:39.6065663Z return parser.parse_args() 2025-10-10T00:06:39.6065969Z 2025-10-10T00:06:39.6065976Z 2025-10-10T00:06:39.6066380Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:06:39.6067185Z auth = Auth.Token(github_token) 2025-10-10T00:06:39.6067688Z return Github(auth=auth) 2025-10-10T00:06:39.6067985Z 2025-10-10T00:06:39.6067992Z 2025-10-10T00:06:39.6068448Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:06:39.6069254Z repo = gh.get_repo(repo) 2025-10-10T00:06:39.6069752Z return repo.get_issue(number=issue_num) 2025-10-10T00:06:39.6070123Z 2025-10-10T00:06:39.6070129Z 2025-10-10T00:06:39.6070315Z def get_potential_pr_author( 2025-10-10T00:06:39.6071180Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:06:39.6071891Z ) -> str: 2025-10-10T00:06:39.6072405Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:06:39.6073208Z # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:06:39.6073953Z # embedded in the tag name: ciflow// 2025-10-10T00:06:39.6074367Z 2025-10-10T00:06:39.6074560Z gh = get_gh_client(github_token) 2025-10-10T00:06:39.6074898Z 2025-10-10T00:06:39.6075165Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:06:39.6075786Z split_tag = ref_name.split("/") 2025-10-10T00:06:39.6076299Z if ( 2025-10-10T00:06:39.6076694Z len(split_tag) == 3 2025-10-10T00:06:39.6077189Z and split_tag[0] == "ciflow" 2025-10-10T00:06:39.6077724Z and split_tag[2].isnumeric() 2025-10-10T00:06:39.6078221Z ): 2025-10-10T00:06:39.6078609Z pr_number = split_tag[2] 2025-10-10T00:06:39.6079262Z try: 2025-10-10T00:06:39.6079702Z repository = gh.get_repo(repo) 2025-10-10T00:06:39.6080314Z pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:06:39.6081030Z except Exception as e: 2025-10-10T00:06:39.6081552Z raise Exception( # noqa: TRY002 2025-10-10T00:06:39.6082229Z f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:06:39.6082869Z ) from e 2025-10-10T00:06:39.6083404Z return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:06:39.6084104Z # In all other cases, return the original input username 2025-10-10T00:06:39.6084693Z return username 2025-10-10T00:06:39.6084942Z 2025-10-10T00:06:39.6084948Z 2025-10-10T00:06:39.6085171Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:06:39.6085701Z """ 2025-10-10T00:06:39.6086341Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:06:39.6087136Z """ 2025-10-10T00:06:39.6087691Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:06:39.6088213Z 2025-10-10T00:06:39.6088220Z 2025-10-10T00:06:39.6088425Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:06:39.6088945Z try: 2025-10-10T00:06:39.6089395Z data = yaml.safe_load(yaml_text) 2025-10-10T00:06:39.6089913Z return data 2025-10-10T00:06:39.6090383Z except yaml.YAMLError: 2025-10-10T00:06:39.6090868Z log.exception("Error loading YAML") 2025-10-10T00:06:39.6091670Z raise 2025-10-10T00:06:39.6091905Z 2025-10-10T00:06:39.6091912Z 2025-10-10T00:06:39.6092336Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:06:39.6093076Z """ 2025-10-10T00:06:39.6093717Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:06:39.6094324Z 2025-10-10T00:06:39.6094824Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:39.6095641Z and the text below is the list of opted in users. 2025-10-10T00:06:39.6096110Z 2025-10-10T00:06:39.6096492Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:06:39.6097192Z """ 2025-10-10T00:06:39.6097645Z rollout_state_parts = rollout_state.split("---") 2025-10-10T00:06:39.6098247Z if len(rollout_state_parts) >= 2: 2025-10-10T00:06:39.6098857Z return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:06:39.6099450Z else: 2025-10-10T00:06:39.6099842Z return "", rollout_state 2025-10-10T00:06:39.6100155Z 2025-10-10T00:06:39.6100162Z 2025-10-10T00:06:39.6100375Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:06:39.6100882Z """ 2025-10-10T00:06:39.6101553Z Dictionary of users with a list of features they have opted into 2025-10-10T00:06:39.6102202Z """ 2025-10-10T00:06:39.6102411Z 2025-10-10T00:06:39.6102424Z 2025-10-10T00:06:39.6102766Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:06:39.6103422Z """ 2025-10-10T00:06:39.6104136Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:06:39.6104819Z 2025-10-10T00:06:39.6105462Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:06:39.6106459Z - Example line: "@User1,lf,split_build" 2025-10-10T00:06:39.6107156Z - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:06:39.6107662Z 2025-10-10T00:06:39.6107668Z 2025-10-10T00:06:39.6107831Z """ 2025-10-10T00:06:39.6108217Z optins = UserOptins() 2025-10-10T00:06:39.6108716Z for user in user_optin_text.split("\n"): 2025-10-10T00:06:39.6109284Z user = user.strip("\r\n\t -") 2025-10-10T00:06:39.6109846Z if not user or not user.startswith("@"): 2025-10-10T00:06:39.6110560Z # Not a valid user. Skip 2025-10-10T00:06:39.6111340Z continue 2025-10-10T00:06:39.6111608Z 2025-10-10T00:06:39.6111770Z if user: 2025-10-10T00:06:39.6112221Z usr_name = user.split(",")[0].strip("@") 2025-10-10T00:06:39.6112927Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:06:39.6113437Z 2025-10-10T00:06:39.6113608Z return optins 2025-10-10T00:06:39.6113851Z 2025-10-10T00:06:39.6113858Z 2025-10-10T00:06:39.6114154Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:06:39.6114757Z """ 2025-10-10T00:06:39.6115162Z Check if the experiment name is valid. 2025-10-10T00:06:39.6115690Z A valid name: 2025-10-10T00:06:39.6116341Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:06:39.6117289Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:06:39.6118028Z - Cannot contain spaces 2025-10-10T00:06:39.6118488Z """ 2025-10-10T00:06:39.6118701Z 2025-10-10T00:06:39.6118962Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:06:39.6119671Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:06:39.6120116Z 2025-10-10T00:06:39.6120275Z if valid: 2025-10-10T00:06:39.6120658Z return True 2025-10-10T00:06:39.6121600Z 2025-10-10T00:06:39.6121800Z log.error( 2025-10-10T00:06:39.6123308Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-10-10T00:06:39.6124989Z ) 2025-10-10T00:06:39.6125348Z return False 2025-10-10T00:06:39.6125594Z 2025-10-10T00:06:39.6125601Z 2025-10-10T00:06:39.6125912Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:06:39.6126564Z """ 2025-10-10T00:06:39.6127408Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:06:39.6128177Z """ 2025-10-10T00:06:39.6128533Z try: 2025-10-10T00:06:39.6128915Z if settings_text: 2025-10-10T00:06:39.6129651Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:06:39.6130455Z # for easy reading 2025-10-10T00:06:39.6131496Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:06:39.6132433Z # the backtick character in shell commands. 2025-10-10T00:06:39.6133046Z backtick = chr(96) # backtick character 2025-10-10T00:06:39.6133725Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:06:39.6134398Z settings = load_yaml(settings_text) 2025-10-10T00:06:39.6134773Z 2025-10-10T00:06:39.6135207Z # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:06:39.6135985Z experiments = {} 2025-10-10T00:06:39.6136286Z 2025-10-10T00:06:39.6136694Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:06:39.6137468Z if not is_valid_experiment_name(exp_name): 2025-10-10T00:06:39.6138606Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-10-10T00:06:39.6139673Z continue 2025-10-10T00:06:39.6139961Z 2025-10-10T00:06:39.6140157Z valid_settings = {} 2025-10-10T00:06:39.6140683Z for setting in exp_settings: 2025-10-10T00:06:39.6141388Z if setting not in Experiment._fields: 2025-10-10T00:06:39.6141956Z log.warning( 2025-10-10T00:06:39.6142672Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:06:39.6143584Z ) 2025-10-10T00:06:39.6144020Z else: 2025-10-10T00:06:39.6144548Z valid_settings[setting] = exp_settings[setting] 2025-10-10T00:06:39.6144979Z 2025-10-10T00:06:39.6145262Z experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:06:39.6145905Z return Settings(experiments) 2025-10-10T00:06:39.6146269Z 2025-10-10T00:06:39.6146447Z except Exception: 2025-10-10T00:06:39.6146941Z log.exception("Failed to parse settings") 2025-10-10T00:06:39.6147336Z 2025-10-10T00:06:39.6147519Z return Settings() 2025-10-10T00:06:39.6147776Z 2025-10-10T00:06:39.6147783Z 2025-10-10T00:06:39.6148029Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:06:39.6148605Z """ 2025-10-10T00:06:39.6149044Z Parse settings, if any, from the rollout state. 2025-10-10T00:06:39.6149462Z 2025-10-10T00:06:39.6149815Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:39.6150600Z and the text below is the list of opted in users. 2025-10-10T00:06:39.6151276Z 2025-10-10T00:06:39.6151722Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:06:39.6152500Z """ 2025-10-10T00:06:39.6153068Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:39.6153858Z return parse_settings_from_text(settings_text) 2025-10-10T00:06:39.6154268Z 2025-10-10T00:06:39.6154275Z 2025-10-10T00:06:39.6154527Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:06:39.6155106Z """ 2025-10-10T00:06:39.6155524Z Parse users from the rollout state. 2025-10-10T00:06:39.6155881Z 2025-10-10T00:06:39.6156044Z """ 2025-10-10T00:06:39.6156591Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:39.6157345Z return parse_user_opt_in_from_text(users_text) 2025-10-10T00:06:39.6157769Z 2025-10-10T00:06:39.6157775Z 2025-10-10T00:06:39.6158348Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:39.6159125Z """ 2025-10-10T00:06:39.6159555Z Check if a user is opted into an experiment 2025-10-10T00:06:39.6160108Z """ 2025-10-10T00:06:39.6160564Z return experiment_name in user_optins.get(user, []) 2025-10-10T00:06:39.6161146Z 2025-10-10T00:06:39.6161152Z 2025-10-10T00:06:39.6161593Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:39.6162353Z """ 2025-10-10T00:06:39.6162825Z Check if a user explicitly opted out of an experiment 2025-10-10T00:06:39.6163414Z """ 2025-10-10T00:06:39.6163935Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:06:39.6164649Z experiment_optout = "-" + experiment_name 2025-10-10T00:06:39.6165291Z if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:06:39.6165915Z return False 2025-10-10T00:06:39.6166170Z 2025-10-10T00:06:39.6166448Z if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:06:39.6167102Z log.warning( 2025-10-10T00:06:39.6167940Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:06:39.6168856Z ) 2025-10-10T00:06:39.6169066Z 2025-10-10T00:06:39.6169230Z return True 2025-10-10T00:06:39.6169486Z 2025-10-10T00:06:39.6169492Z 2025-10-10T00:06:39.6169668Z def get_runner_prefix( 2025-10-10T00:06:39.6170120Z rollout_state: str, 2025-10-10T00:06:39.6170583Z workflow_requestors: Iterable[str], 2025-10-10T00:06:39.6171227Z branch: str, 2025-10-10T00:06:39.6171712Z eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:39.6172384Z opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:39.6172972Z is_canary: bool = False, 2025-10-10T00:06:39.6173432Z ) -> str: 2025-10-10T00:06:39.6174045Z settings = parse_settings(rollout_state) 2025-10-10T00:06:39.6174656Z user_optins = parse_users(rollout_state) 2025-10-10T00:06:39.6175030Z 2025-10-10T00:06:39.6175214Z fleet_prefix = "" 2025-10-10T00:06:39.6175640Z prefixes = [] 2025-10-10T00:06:39.6176283Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:06:39.6177229Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:06:39.6177954Z log.info( 2025-10-10T00:06:39.6178636Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:06:39.6179409Z ) 2025-10-10T00:06:39.6179797Z continue 2025-10-10T00:06:39.6180046Z 2025-10-10T00:06:39.6180239Z if opt_out_experiments: 2025-10-10T00:06:39.6180781Z if experiment_name in opt_out_experiments: 2025-10-10T00:06:39.6181560Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:06:39.6182168Z log.info( 2025-10-10T00:06:39.6183098Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:06:39.6184086Z ) 2025-10-10T00:06:39.6184488Z continue 2025-10-10T00:06:39.6184768Z 2025-10-10T00:06:39.6184955Z if eligible_experiments: 2025-10-10T00:06:39.6185515Z if experiment_name not in eligible_experiments: 2025-10-10T00:06:39.6186151Z exp_list = ", ".join(eligible_experiments) 2025-10-10T00:06:39.6186720Z log.info( 2025-10-10T00:06:39.6187505Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:06:39.6188360Z ) 2025-10-10T00:06:39.6188754Z continue 2025-10-10T00:06:39.6189255Z elif not experiment_settings.default: 2025-10-10T00:06:39.6189822Z log.info( 2025-10-10T00:06:39.6190650Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:06:39.6191559Z ) 2025-10-10T00:06:39.6191941Z continue 2025-10-10T00:06:39.6192198Z 2025-10-10T00:06:39.6192477Z # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:06:39.6193105Z opted_out_users = [ 2025-10-10T00:06:39.6193555Z requestor 2025-10-10T00:06:39.6194011Z for requestor in workflow_requestors 2025-10-10T00:06:39.6194695Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:06:39.6195335Z ] 2025-10-10T00:06:39.6195546Z 2025-10-10T00:06:39.6195730Z if opted_out_users: 2025-10-10T00:06:39.6196185Z log.info( 2025-10-10T00:06:39.6196805Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:06:39.6197523Z ) 2025-10-10T00:06:39.6197904Z continue 2025-10-10T00:06:39.6198173Z 2025-10-10T00:06:39.6198449Z # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:06:39.6199059Z opted_in_users = [ 2025-10-10T00:06:39.6199507Z requestor 2025-10-10T00:06:39.6199973Z for requestor in workflow_requestors 2025-10-10T00:06:39.6200629Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:06:39.6201365Z ] 2025-10-10T00:06:39.6201573Z 2025-10-10T00:06:39.6201743Z enabled = False 2025-10-10T00:06:39.6202188Z if opted_in_users: 2025-10-10T00:06:39.6202625Z log.info( 2025-10-10T00:06:39.6203234Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:06:39.6203922Z ) 2025-10-10T00:06:39.6204322Z enabled = True 2025-10-10T00:06:39.6204604Z 2025-10-10T00:06:39.6204830Z elif experiment_settings.rollout_perc: 2025-10-10T00:06:39.6205681Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:06:39.6207095Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:06:39.6207758Z log.info( 2025-10-10T00:06:39.6208640Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:06:39.6209570Z ) 2025-10-10T00:06:39.6209986Z enabled = True 2025-10-10T00:06:39.6210290Z 2025-10-10T00:06:39.6210466Z if enabled: 2025-10-10T00:06:39.6210891Z label = experiment_name 2025-10-10T00:06:39.6211574Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:06:39.6212425Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:06:39.6213319Z # - If it's enabled, then we always list it's prefix first 2025-10-10T00:06:39.6214092Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:06:39.6214791Z if is_canary: 2025-10-10T00:06:39.6215293Z label += CANARY_FLEET_SUFFIX 2025-10-10T00:06:39.6215848Z fleet_prefix = label 2025-10-10T00:06:39.6216347Z else: 2025-10-10T00:06:39.6216776Z prefixes.append(label) 2025-10-10T00:06:39.6217137Z 2025-10-10T00:06:39.6217319Z if len(prefixes) > 1: 2025-10-10T00:06:39.6217768Z log.error( 2025-10-10T00:06:39.6218820Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-10-10T00:06:39.6219965Z ) 2025-10-10T00:06:39.6220364Z prefixes = prefixes[:1] 2025-10-10T00:06:39.6220675Z 2025-10-10T00:06:39.6220874Z # Fleet always comes first 2025-10-10T00:06:39.6221462Z if fleet_prefix: 2025-10-10T00:06:39.6221920Z prefixes.insert(0, fleet_prefix) 2025-10-10T00:06:39.6222295Z 2025-10-10T00:06:39.6222686Z return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:06:39.6223132Z 2025-10-10T00:06:39.6223139Z 2025-10-10T00:06:39.6223587Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:06:39.6224375Z """ 2025-10-10T00:06:39.6224969Z Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:06:39.6225535Z 2025-10-10T00:06:39.6225929Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:06:39.6226636Z """ 2025-10-10T00:06:39.6227032Z gh = get_gh_client(github_token) 2025-10-10T00:06:39.6227581Z issue = get_issue(gh, repo, issue_num) 2025-10-10T00:06:39.6228220Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:06:39.6228688Z 2025-10-10T00:06:39.6228695Z 2025-10-10T00:06:39.6229100Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:06:39.6229870Z for _ in range(num_retries): 2025-10-10T00:06:39.6230372Z try: 2025-10-10T00:06:39.6230801Z req = Request(url=url, headers=headers) 2025-10-10T00:06:39.6231594Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:06:39.6232237Z return json.loads(content) 2025-10-10T00:06:39.6232773Z except Exception as e: 2025-10-10T00:06:39.6233312Z log.warning(f"Could not download {url}: {e}") 2025-10-10T00:06:39.6233724Z 2025-10-10T00:06:39.6234098Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:06:39.6234820Z return {} 2025-10-10T00:06:39.6235045Z 2025-10-10T00:06:39.6235052Z 2025-10-10T00:06:39.6235211Z @cache 2025-10-10T00:06:39.6235839Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:06:39.6236610Z """ 2025-10-10T00:06:39.6237011Z Dynamically get PR information 2025-10-10T00:06:39.6237638Z """ 2025-10-10T00:06:39.6238145Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:06:39.6238783Z headers = { 2025-10-10T00:06:39.6239240Z "Accept": "application/vnd.github.v3+json", 2025-10-10T00:06:39.6239853Z "Authorization": f"token {github_token}", 2025-10-10T00:06:39.6240393Z } 2025-10-10T00:06:39.6240843Z json_response: dict[str, Any] = download_json( 2025-10-10T00:06:39.6241578Z url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:06:39.6242145Z headers=headers, 2025-10-10T00:06:39.6242570Z ) 2025-10-10T00:06:39.6242779Z 2025-10-10T00:06:39.6242959Z if not json_response: 2025-10-10T00:06:39.6243529Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:06:39.6244149Z return {} 2025-10-10T00:06:39.6244385Z 2025-10-10T00:06:39.6244573Z return json_response 2025-10-10T00:06:39.6244856Z 2025-10-10T00:06:39.6244862Z 2025-10-10T00:06:39.6245264Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:06:39.6246006Z """ 2025-10-10T00:06:39.6246537Z Dynamically get the latest list of labels from the pull request 2025-10-10T00:06:39.6247223Z """ 2025-10-10T00:06:39.6247707Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:06:39.6248330Z return { 2025-10-10T00:06:39.6248930Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:06:39.6249631Z } 2025-10-10T00:06:39.6249832Z 2025-10-10T00:06:39.6249838Z 2025-10-10T00:06:39.6250016Z def main() -> None: 2025-10-10T00:06:39.6250440Z args = parse_args() 2025-10-10T00:06:39.6250718Z 2025-10-10T00:06:39.6251043Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:06:39.6251443Z 2025-10-10T00:06:39.6251638Z # Check if the PR is opt-out 2025-10-10T00:06:39.6252143Z if args.pr_number: 2025-10-10T00:06:39.6252798Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:06:39.6253845Z if OPT_OUT_LABEL in labels: 2025-10-10T00:06:39.6254371Z log.info( 2025-10-10T00:06:39.6255060Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:06:39.6255831Z ) 2025-10-10T00:06:39.6256376Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:39.6257050Z sys.exit() 2025-10-10T00:06:39.6257311Z 2025-10-10T00:06:39.6257477Z try: 2025-10-10T00:06:39.6257916Z rollout_state = get_rollout_state_from_issue( 2025-10-10T00:06:39.6258634Z args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:06:39.6259277Z ) 2025-10-10T00:06:39.6259484Z 2025-10-10T00:06:39.6259696Z username = get_potential_pr_author( 2025-10-10T00:06:39.6260245Z args.github_token, 2025-10-10T00:06:39.6260732Z args.github_repo, 2025-10-10T00:06:39.6261321Z args.github_actor, 2025-10-10T00:06:39.6261821Z args.github_ref_type, 2025-10-10T00:06:39.6262328Z args.github_branch, 2025-10-10T00:06:39.6262785Z ) 2025-10-10T00:06:39.6262993Z 2025-10-10T00:06:39.6263286Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:06:39.6263742Z 2025-10-10T00:06:39.6263961Z runner_label_prefix = get_runner_prefix( 2025-10-10T00:06:39.6264524Z rollout_state, 2025-10-10T00:06:39.6265006Z (args.github_issue_owner, username), 2025-10-10T00:06:39.6265562Z args.github_branch, 2025-10-10T00:06:39.6266059Z args.eligible_experiments, 2025-10-10T00:06:39.6266608Z args.opt_out_experiments, 2025-10-10T00:06:39.6267150Z is_canary, 2025-10-10T00:06:39.6267564Z ) 2025-10-10T00:06:39.6267770Z 2025-10-10T00:06:39.6267961Z except Exception as e: 2025-10-10T00:06:39.6268419Z log.error( 2025-10-10T00:06:39.6269103Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:06:39.6270062Z ) 2025-10-10T00:06:39.6270280Z 2025-10-10T00:06:39.6270610Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:39.6271231Z 2025-10-10T00:06:39.6271237Z 2025-10-10T00:06:39.6271428Z if __name__ == "__main__": 2025-10-10T00:06:39.6271871Z main() 2025-10-10T00:06:39.6272084Z 2025-10-10T00:06:39.6370230Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:06:39.6371548Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:06:39.6405087Z shell: /usr/bin/bash -e {0} 2025-10-10T00:06:39.6405580Z env: 2025-10-10T00:06:39.6406282Z GITHUB_TOKEN: *** 2025-10-10T00:06:39.6406707Z ISSUE_NUMBER: 5132 2025-10-10T00:06:39.6407162Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:06:39.6407657Z ISSUE_OWNER: 2025-10-10T00:06:39.6408057Z CHECK_EXPERIMENTS: 2025-10-10T00:06:39.6408497Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:06:39.6408940Z PR_NUMBER: 2025-10-10T00:06:39.6409314Z ##[endgroup] 2025-10-10T00:06:40.2504259Z Defaulting to user installation because normal site-packages is not writeable 2025-10-10T00:06:41.0867849Z Collecting urllib3==1.26.18 2025-10-10T00:06:41.1868305Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-10-10T00:06:41.2199293Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.1 MB/s eta 0:00:00 2025-10-10T00:06:41.2717526Z Collecting PyGithub==2.3.0 2025-10-10T00:06:41.2930510Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-10-10T00:06:41.3585650Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-10-10T00:06:41.3786757Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-10-10T00:06:41.3841315Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-10-10T00:06:41.3861348Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-10-10T00:06:41.3876455Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-10-10T00:06:41.4369162Z Collecting Deprecated (from PyGithub==2.3.0) 2025-10-10T00:06:41.4578244Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-10-10T00:06:41.4824911Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-10-10T00:06:41.6553650Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:06:41.6749796Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-10-10T00:06:41.8442159Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-10-10T00:06:41.8643781Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-10-10T00:06:41.9010212Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:06:41.9212795Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-10-10T00:06:41.9643583Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-10-10T00:06:41.9885290Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 6.1 MB/s eta 0:00:00 2025-10-10T00:06:42.0103535Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-10-10T00:06:42.0338774Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 16.1 MB/s eta 0:00:00 2025-10-10T00:06:42.0533206Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-10-10T00:06:42.0841963Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 48.5 MB/s eta 0:00:00 2025-10-10T00:06:42.1040459Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-10-10T00:06:42.1297523Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-10-10T00:06:42.1351288Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 59.9 MB/s eta 0:00:00 2025-10-10T00:06:42.1549034Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-10-10T00:06:42.1602248Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 23.1 MB/s eta 0:00:00 2025-10-10T00:06:42.1794736Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-10-10T00:06:42.1842429Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 36.7 MB/s eta 0:00:00 2025-10-10T00:06:42.4891569Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-10-10T00:06:43.0553933Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.0 urllib3-1.26.18 wrapt-1.17.3 2025-10-10T00:06:43.1893467Z ##[group]Run curr_branch="main" 2025-10-10T00:06:43.1893791Z curr_branch="main" 2025-10-10T00:06:43.1894015Z curr_ref_type="branch" 2025-10-10T00:06:43.1894271Z echo "Current branch is '$curr_branch'" 2025-10-10T00:06:43.1894553Z  2025-10-10T00:06:43.1894764Z python3 runner_determinator.py \ 2025-10-10T00:06:43.1895055Z  --github-token "$GITHUB_TOKEN" \ 2025-10-10T00:06:43.1895338Z  --github-issue "$ISSUE_NUMBER" \ 2025-10-10T00:06:43.1895591Z  --github-branch "$curr_branch" \ 2025-10-10T00:06:43.1895853Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-10-10T00:06:43.1896124Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-10-10T00:06:43.1896397Z  --github-ref-type "$curr_ref_type" \ 2025-10-10T00:06:43.1896668Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-10-10T00:06:43.1896957Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-10-10T00:06:43.1897328Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-10-10T00:06:43.1897622Z  --pr-number "${PR_NUMBER}" 2025-10-10T00:06:43.1932508Z shell: /usr/bin/bash -e {0} 2025-10-10T00:06:43.1932734Z env: 2025-10-10T00:06:43.1933354Z GITHUB_TOKEN: *** 2025-10-10T00:06:43.1933554Z ISSUE_NUMBER: 5132 2025-10-10T00:06:43.1933750Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:06:43.1933987Z ISSUE_OWNER: 2025-10-10T00:06:43.1934161Z CHECK_EXPERIMENTS: 2025-10-10T00:06:43.1934360Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:06:43.1934539Z PR_NUMBER: 2025-10-10T00:06:43.1934704Z ##[endgroup] 2025-10-10T00:06:43.1988724Z Current branch is 'main' 2025-10-10T00:06:45.2487225Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-10-10T00:06:45.2488385Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-10-10T00:06:45.2489320Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-10-10T00:06:45.2489995Z INFO : Setting output: label-type='' 2025-10-10T00:06:45.2864673Z Evaluate and set job outputs 2025-10-10T00:06:45.2872201Z Cleaning up orphan processes