2025-11-03T15:48:17.1765834Z Current runner version: '2.329.0' 2025-11-03T15:48:17.1788143Z ##[group]Runner Image Provisioner 2025-11-03T15:48:17.1788915Z Hosted Compute Agent 2025-11-03T15:48:17.1789467Z Version: 20251016.436 2025-11-03T15:48:17.1790094Z Commit: 8ab8ac8bfd662a3739dab9fe09456aba92132568 2025-11-03T15:48:17.1790807Z Build Date: 2025-10-15T20:44:12Z 2025-11-03T15:48:17.1791396Z ##[endgroup] 2025-11-03T15:48:17.1791920Z ##[group]Operating System 2025-11-03T15:48:17.1792530Z Ubuntu 2025-11-03T15:48:17.1792970Z 24.04.3 2025-11-03T15:48:17.1793472Z LTS 2025-11-03T15:48:17.1793908Z ##[endgroup] 2025-11-03T15:48:17.1794393Z ##[group]Runner Image 2025-11-03T15:48:17.1794872Z Image: ubuntu-24.04 2025-11-03T15:48:17.1795491Z Version: 20251030.96.2 2025-11-03T15:48:17.1796640Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251030.96/images/ubuntu/Ubuntu2404-Readme.md 2025-11-03T15:48:17.1798196Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251030.96 2025-11-03T15:48:17.1799142Z ##[endgroup] 2025-11-03T15:48:17.1800202Z ##[group]GITHUB_TOKEN Permissions 2025-11-03T15:48:17.1802272Z Contents: read 2025-11-03T15:48:17.1802807Z Metadata: read 2025-11-03T15:48:17.1803354Z ##[endgroup] 2025-11-03T15:48:17.1805272Z Secret source: Actions 2025-11-03T15:48:17.1805910Z Prepare workflow directory 2025-11-03T15:48:17.2324745Z Prepare all required actions 2025-11-03T15:48:17.2404604Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (3f6538febd727b782e6e13cfd026a309fb14351d) 2025-11-03T15:48:17.2409581Z ##[group] Inputs 2025-11-03T15:48:17.2410145Z check_experiments: 2025-11-03T15:48:17.2410871Z opt_out_experiments: 2025-11-03T15:48:17.2411418Z triggering_actor: pytorchmergebot 2025-11-03T15:48:17.2412010Z issue_owner: 2025-11-03T15:48:17.2412549Z curr_branch: main 2025-11-03T15:48:17.2413080Z curr_ref_type: branch 2025-11-03T15:48:17.2413694Z issue_number: 5132 2025-11-03T15:48:17.2414289Z ##[endgroup] 2025-11-03T15:48:17.2414970Z Complete job name: before-test / get-label-type / runner-determinator 2025-11-03T15:48:18.0125723Z ##[group]Run cat < runner_determinator.py 2025-11-03T15:48:18.0128925Z cat < runner_determinator.py 2025-11-03T15:48:18.0130183Z # flake8: noqa: G004 2025-11-03T15:48:18.0131229Z  2025-11-03T15:48:18.0132801Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-11-03T15:48:18.0135113Z # must be kept in sync. You can do it easily by running the following command: 2025-11-03T15:48:18.0137342Z # python .github/scripts/update_runner_determinator.py 2025-11-03T15:48:18.0138911Z  2025-11-03T15:48:18.0139773Z """ 2025-11-03T15:48:18.0141169Z This runner determinator is used to determine which set of runners to run a 2025-11-03T15:48:18.0143341Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-11-03T15:48:18.0145720Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-11-03T15:48:18.0147888Z of which runners should be used to run which job. 2025-11-03T15:48:18.0149211Z  2025-11-03T15:48:18.0150575Z The configuration has two parts, the settings and a list of opted-in users, 2025-11-03T15:48:18.0152745Z separated by a line containing "---". If the line is not present, the 2025-11-03T15:48:18.0154794Z settings are considered to be empty with only the second part, the user 2025-11-03T15:48:18.0156565Z list, defined. 2025-11-03T15:48:18.0157466Z  2025-11-03T15:48:18.0158863Z The first part is a YAML block that defines the rollout settings. This can be 2025-11-03T15:48:18.0161058Z used to define any settings that are needed to determine which runners to use. 2025-11-03T15:48:18.0162995Z It's fields are defined by the RolloutSettings class below. 2025-11-03T15:48:18.0164482Z  2025-11-03T15:48:18.0166080Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-11-03T15:48:18.0168270Z The user list is also a comma separated list of additional features or 2025-11-03T15:48:18.0170127Z experiments which the user could be opted in to. 2025-11-03T15:48:18.0171406Z  2025-11-03T15:48:18.0172287Z The user list has the following rules: 2025-11-03T15:48:18.0173486Z  2025-11-03T15:48:18.0174713Z - Users are GitHub usernames, which must start with the @ prefix 2025-11-03T15:48:18.0176798Z - Each user is also a comma-separated list of features/experiments to enable 2025-11-03T15:48:18.0178681Z - A "#" prefix opts the user out of all experiments 2025-11-03T15:48:18.0179929Z  2025-11-03T15:48:18.0180695Z Example config: 2025-11-03T15:48:18.0181868Z  # A list of experiments that can be opted into. 2025-11-03T15:48:18.0183439Z  # This defines the behavior they'll induce when opted into. 2025-11-03T15:48:18.0184916Z  # Expected syntax is: 2025-11-03T15:48:18.0186729Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-11-03T15:48:18.0188959Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-11-03T15:48:18.0190703Z  2025-11-03T15:48:18.0276415Z  experiments: 2025-11-03T15:48:18.0277676Z  lf: 2025-11-03T15:48:18.0278511Z  rollout_percent: 25 2025-11-03T15:48:18.0279539Z  all_branches: false 2025-11-03T15:48:18.0280563Z  default: true 2025-11-03T15:48:18.0281467Z  --- 2025-11-03T15:48:18.0282218Z  2025-11-03T15:48:18.0282924Z  # Opt-ins: 2025-11-03T15:48:18.0284268Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-11-03T15:48:18.0286574Z  # and specifying experiments to enable in a comma-separated list. 2025-11-03T15:48:18.0288372Z  # To always opt out of an experiment, prefix it with a "-". 2025-11-03T15:48:18.0289840Z  # Experiments should be from the above list. 2025-11-03T15:48:18.0291003Z  2025-11-03T15:48:18.0291755Z  @User1,-lf,split_build 2025-11-03T15:48:18.0292884Z  @User2,lf 2025-11-03T15:48:18.0294214Z  @User3,split_build 2025-11-03T15:48:18.0295733Z """ 2025-11-03T15:48:18.0297081Z  2025-11-03T15:48:18.0298212Z import json 2025-11-03T15:48:18.0299410Z import logging 2025-11-03T15:48:18.0301000Z import os 2025-11-03T15:48:18.0302234Z import random 2025-11-03T15:48:18.0303599Z import re 2025-11-03T15:48:18.0304386Z import sys 2025-11-03T15:48:18.0305286Z from argparse import ArgumentParser 2025-11-03T15:48:18.0306674Z from collections.abc import Iterable 2025-11-03T15:48:18.0307885Z from functools import cache 2025-11-03T15:48:18.0308939Z from logging import LogRecord 2025-11-03T15:48:18.0310042Z from typing import Any, NamedTuple 2025-11-03T15:48:18.0311326Z from urllib.request import Request, urlopen 2025-11-03T15:48:18.0312459Z  2025-11-03T15:48:18.0313150Z import yaml 2025-11-03T15:48:18.0314007Z from github import Auth, Github 2025-11-03T15:48:18.0315098Z from github.Issue import Issue 2025-11-03T15:48:18.0316101Z  2025-11-03T15:48:18.0316952Z  2025-11-03T15:48:18.0317822Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-11-03T15:48:18.0319361Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-11-03T15:48:18.0321272Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-11-03T15:48:18.0322793Z  2025-11-03T15:48:18.0323871Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-11-03T15:48:18.0325104Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-11-03T15:48:18.0326232Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-11-03T15:48:18.0327592Z OPT_OUT_LABEL = "no-runner-experiments" 2025-11-03T15:48:18.0328673Z  2025-11-03T15:48:18.0329450Z SETTING_EXPERIMENTS = "experiments" 2025-11-03T15:48:18.0330489Z  2025-11-03T15:48:18.0331209Z LF_FLEET_EXPERIMENT = "lf" 2025-11-03T15:48:18.0332221Z CANARY_FLEET_SUFFIX = ".c" 2025-11-03T15:48:18.0333148Z  2025-11-03T15:48:18.0333792Z  2025-11-03T15:48:18.0334526Z class Experiment(NamedTuple): 2025-11-03T15:48:18.0335567Z  rollout_perc: float = ( 2025-11-03T15:48:18.0337091Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-11-03T15:48:18.0338539Z  ) 2025-11-03T15:48:18.0339287Z  all_branches: bool = ( 2025-11-03T15:48:18.0340697Z  False # If True, the experiment is also enabled on the exception branches 2025-11-03T15:48:18.0342106Z  ) 2025-11-03T15:48:18.0342828Z  default: bool = ( 2025-11-03T15:48:18.0344097Z  True # If True, the experiment is enabled by default for all queries 2025-11-03T15:48:18.0345446Z  ) 2025-11-03T15:48:18.0346138Z  2025-11-03T15:48:18.0347071Z  # Add more fields as needed 2025-11-03T15:48:18.0348029Z  2025-11-03T15:48:18.0348672Z  2025-11-03T15:48:18.0349379Z class Settings(NamedTuple): 2025-11-03T15:48:18.0350324Z  """ 2025-11-03T15:48:18.0351301Z  Settings for the experiments that can be opted into. 2025-11-03T15:48:18.0352509Z  """ 2025-11-03T15:48:18.0353190Z  2025-11-03T15:48:18.0353979Z  experiments: dict[str, Experiment] = {} 2025-11-03T15:48:18.0355045Z  2025-11-03T15:48:18.0355830Z  2025-11-03T15:48:18.0356865Z class ColorFormatter(logging.Formatter): 2025-11-03T15:48:18.0358250Z  """Color codes the log messages based on the log level""" 2025-11-03T15:48:18.0359485Z  2025-11-03T15:48:18.0360144Z  COLORS = { 2025-11-03T15:48:18.0361000Z  "WARNING": "\033[33m", # Yellow 2025-11-03T15:48:18.0362088Z  "ERROR": "\033[31m", # Red 2025-11-03T15:48:18.0363234Z  "CRITICAL": "\033[31m", # Red 2025-11-03T15:48:18.0364331Z  "INFO": "\033[0m", # Reset 2025-11-03T15:48:18.0365400Z  "DEBUG": "\033[0m", # Reset 2025-11-03T15:48:18.0366364Z  } 2025-11-03T15:48:18.0367160Z  2025-11-03T15:48:18.0367982Z  def format(self, record: LogRecord) -> str: 2025-11-03T15:48:18.0369602Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-11-03T15:48:18.0371247Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-11-03T15:48:18.0372446Z  return super().format(record) 2025-11-03T15:48:18.0373424Z  2025-11-03T15:48:18.0374066Z  2025-11-03T15:48:18.0374809Z handler = logging.StreamHandler() 2025-11-03T15:48:18.0376351Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-11-03T15:48:18.0377910Z  2025-11-03T15:48:18.0378810Z log = logging.getLogger(os.path.basename(__file__)) 2025-11-03T15:48:18.0380031Z log.addHandler(handler) 2025-11-03T15:48:18.0380976Z log.setLevel(logging.INFO) 2025-11-03T15:48:18.0381895Z  2025-11-03T15:48:18.0382533Z  2025-11-03T15:48:18.0383451Z def set_github_output(key: str, value: str) -> None: 2025-11-03T15:48:18.0384654Z  """ 2025-11-03T15:48:18.0385742Z  Defines outputs of the github action that invokes this script 2025-11-03T15:48:18.0387721Z  """ 2025-11-03T15:48:18.0388456Z  if not GITHUB_OUTPUT: 2025-11-03T15:48:18.0390787Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-11-03T15:48:18.0393184Z  log.warning( 2025-11-03T15:48:18.0394982Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-11-03T15:48:18.0397103Z  ) 2025-11-03T15:48:18.0398001Z  print(f"::set-output name={key}::{value}") 2025-11-03T15:48:18.0399083Z  return 2025-11-03T15:48:18.0399850Z  2025-11-03T15:48:18.0400599Z  with open(GITHUB_OUTPUT, "a") as f: 2025-11-03T15:48:18.0401780Z  log.info(f"Setting output: {key}='{value}'") 2025-11-03T15:48:18.0402938Z  f.write(f"{key}={value}\n") 2025-11-03T15:48:18.0403900Z  2025-11-03T15:48:18.0404550Z  2025-11-03T15:48:18.0405556Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-11-03T15:48:18.0406956Z  return frozenset( 2025-11-03T15:48:18.0408253Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-11-03T15:48:18.0409618Z  ) 2025-11-03T15:48:18.0410293Z  2025-11-03T15:48:18.0410933Z  2025-11-03T15:48:18.0411655Z def parse_args() -> Any: 2025-11-03T15:48:18.0412849Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-11-03T15:48:18.0414663Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-11-03T15:48:18.0416238Z  parser.add_argument( 2025-11-03T15:48:18.0417293Z  "--github-issue-repo", 2025-11-03T15:48:18.0418253Z  type=str, 2025-11-03T15:48:18.0419111Z  required=False, 2025-11-03T15:48:18.0420213Z  default="pytorch/test-infra", 2025-11-03T15:48:18.0421329Z  help="GitHub repo to get the issue", 2025-11-03T15:48:18.0422347Z  ) 2025-11-03T15:48:18.0423086Z  parser.add_argument( 2025-11-03T15:48:18.0424096Z  "--github-repo", 2025-11-03T15:48:18.0425119Z  type=str, 2025-11-03T15:48:18.0425940Z  required=True, 2025-11-03T15:48:18.0427051Z  help="GitHub repo where CI is running", 2025-11-03T15:48:18.0428088Z  ) 2025-11-03T15:48:18.0428814Z  parser.add_argument( 2025-11-03T15:48:18.0430111Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-11-03T15:48:18.0431447Z  ) 2025-11-03T15:48:18.0432168Z  parser.add_argument( 2025-11-03T15:48:18.0433521Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-11-03T15:48:18.0435046Z  ) 2025-11-03T15:48:18.0435909Z  parser.add_argument( 2025-11-03T15:48:18.0437401Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-11-03T15:48:18.0438814Z  ) 2025-11-03T15:48:18.0439549Z  parser.add_argument( 2025-11-03T15:48:18.0440968Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-11-03T15:48:18.0442415Z  ) 2025-11-03T15:48:18.0443143Z  parser.add_argument( 2025-11-03T15:48:18.0444063Z  "--github-ref-type", 2025-11-03T15:48:18.0444978Z  type=str, 2025-11-03T15:48:18.0445818Z  required=True, 2025-11-03T15:48:18.0446935Z  help="Current GitHub ref type, branch or tag", 2025-11-03T15:48:18.0448033Z  ) 2025-11-03T15:48:18.0448755Z  parser.add_argument( 2025-11-03T15:48:18.0449873Z  "--eligible-experiments", 2025-11-03T15:48:18.0450927Z  type=_str_comma_separated_to_set, 2025-11-03T15:48:18.0451954Z  required=False, 2025-11-03T15:48:18.0452812Z  default="", 2025-11-03T15:48:18.0454614Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-11-03T15:48:18.0456625Z  ) 2025-11-03T15:48:18.0457352Z  parser.add_argument( 2025-11-03T15:48:18.0458313Z  "--opt-out-experiments", 2025-11-03T15:48:18.0459347Z  type=_str_comma_separated_to_set, 2025-11-03T15:48:18.0460364Z  required=False, 2025-11-03T15:48:18.0461226Z  default="", 2025-11-03T15:48:18.0462035Z  help=( 2025-11-03T15:48:18.0463433Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-11-03T15:48:18.0465786Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-11-03T15:48:18.0467589Z  ), 2025-11-03T15:48:18.0468291Z  ) 2025-11-03T15:48:18.0469009Z  parser.add_argument( 2025-11-03T15:48:18.0469906Z  "--pr-number", 2025-11-03T15:48:18.0470767Z  type=str, 2025-11-03T15:48:18.0471582Z  required=False, 2025-11-03T15:48:18.0472451Z  default="", 2025-11-03T15:48:18.0473464Z  help="the optional PR number where this is run", 2025-11-03T15:48:18.0474560Z  ) 2025-11-03T15:48:18.0475223Z  2025-11-03T15:48:18.0475918Z  return parser.parse_args() 2025-11-03T15:48:18.0476946Z  2025-11-03T15:48:18.0477564Z  2025-11-03T15:48:18.0478736Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-11-03T15:48:18.0480391Z  auth = Auth.Token(github_token) 2025-11-03T15:48:18.0481417Z  return Github(auth=auth) 2025-11-03T15:48:18.0482306Z  2025-11-03T15:48:18.0482920Z  2025-11-03T15:48:18.0484213Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-11-03T15:48:18.0485833Z  repo = gh.get_repo(repo) 2025-11-03T15:48:18.0486967Z  return repo.get_issue(number=issue_num) 2025-11-03T15:48:18.0487983Z  2025-11-03T15:48:18.0488602Z  2025-11-03T15:48:18.0489289Z def get_potential_pr_author( 2025-11-03T15:48:18.0490631Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-11-03T15:48:18.0491978Z ) -> str: 2025-11-03T15:48:18.0493062Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-11-03T15:48:18.0494719Z  # Fetch the actual username from the original PR. The PR number is 2025-11-03T15:48:18.0496237Z  # embedded in the tag name: ciflow// 2025-11-03T15:48:18.0497455Z  2025-11-03T15:48:18.0498174Z  gh = get_gh_client(github_token) 2025-11-03T15:48:18.0499118Z  2025-11-03T15:48:18.0500011Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-11-03T15:48:18.0501260Z  split_tag = ref_name.split("/") 2025-11-03T15:48:18.0502237Z  if ( 2025-11-03T15:48:18.0503003Z  len(split_tag) == 3 2025-11-03T15:48:18.0503988Z  and split_tag[0] == "ciflow" 2025-11-03T15:48:18.0505052Z  and split_tag[2].isnumeric() 2025-11-03T15:48:18.0506024Z  ): 2025-11-03T15:48:18.0506902Z  pr_number = split_tag[2] 2025-11-03T15:48:18.0507858Z  try: 2025-11-03T15:48:18.0508717Z  repository = gh.get_repo(repo) 2025-11-03T15:48:18.0510101Z  pull = repository.get_pull(number=int(pr_number)) 2025-11-03T15:48:18.0511345Z  except Exception as e: 2025-11-03T15:48:18.0512390Z  raise Exception( # noqa: TRY002 2025-11-03T15:48:18.0513757Z  f"issue with pull request {pr_number} from repo {repository}" 2025-11-03T15:48:18.0515015Z  ) from e 2025-11-03T15:48:18.0516124Z  return pull.user.login # type: ignore[no-any-return] 2025-11-03T15:48:18.0517641Z  # In all other cases, return the original input username 2025-11-03T15:48:18.0518815Z  return username 2025-11-03T15:48:18.0519634Z  2025-11-03T15:48:18.0520286Z  2025-11-03T15:48:18.0521081Z def is_exception_branch(branch: str) -> bool: 2025-11-03T15:48:18.0522117Z  """ 2025-11-03T15:48:18.0523430Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-11-03T15:48:18.0524981Z  """ 2025-11-03T15:48:18.0526073Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-11-03T15:48:18.0527473Z  2025-11-03T15:48:18.0528093Z  2025-11-03T15:48:18.0528809Z def load_yaml(yaml_text: str) -> Any: 2025-11-03T15:48:18.0529769Z  try: 2025-11-03T15:48:18.0530538Z  data = yaml.safe_load(yaml_text) 2025-11-03T15:48:18.0531518Z  return data 2025-11-03T15:48:18.0532369Z  except yaml.YAMLError: 2025-11-03T15:48:18.0533365Z  log.exception("Error loading YAML") 2025-11-03T15:48:18.0534355Z  raise 2025-11-03T15:48:18.0535077Z  2025-11-03T15:48:18.0535692Z  2025-11-03T15:48:18.0536953Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-11-03T15:48:18.0538390Z  """ 2025-11-03T15:48:18.0539772Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-11-03T15:48:18.0541241Z  2025-11-03T15:48:18.0542289Z  If the issue body contains "---" then the text above that is the settings 2025-11-03T15:48:18.0543794Z  and the text below is the list of opted in users. 2025-11-03T15:48:18.0544868Z  2025-11-03T15:48:18.0545959Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-11-03T15:48:18.0547427Z  """ 2025-11-03T15:48:18.0548309Z  rollout_state_parts = rollout_state.split("---") 2025-11-03T15:48:18.0549480Z  if len(rollout_state_parts) >= 2: 2025-11-03T15:48:18.0550911Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-11-03T15:48:18.0552097Z  else: 2025-11-03T15:48:18.0552855Z  return "", rollout_state 2025-11-03T15:48:18.0553754Z  2025-11-03T15:48:18.0554361Z  2025-11-03T15:48:18.0555099Z class UserOptins(dict[str, list[str]]): 2025-11-03T15:48:18.0556080Z  """ 2025-11-03T15:48:18.0557201Z  Dictionary of users with a list of features they have opted into 2025-11-03T15:48:18.0558435Z  """ 2025-11-03T15:48:18.0559084Z  2025-11-03T15:48:18.0559685Z  2025-11-03T15:48:18.0560684Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-11-03T15:48:18.0561943Z  """ 2025-11-03T15:48:18.0563344Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-11-03T15:48:18.0564968Z  2025-11-03T15:48:18.0566689Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-11-03T15:48:18.0568642Z  - Example line: "@User1,lf,split_build" 2025-11-03T15:48:18.0570115Z  - A "#" prefix indicates the user is opted out of all experiments 2025-11-03T15:48:18.0571324Z  2025-11-03T15:48:18.0571930Z  2025-11-03T15:48:18.0572531Z  """ 2025-11-03T15:48:18.0573243Z  optins = UserOptins() 2025-11-03T15:48:18.0574225Z  for user in user_optin_text.split("\n"): 2025-11-03T15:48:18.0575305Z  user = user.strip("\r\n\t -") 2025-11-03T15:48:18.0576370Z  if not user or not user.startswith("@"): 2025-11-03T15:48:18.0577545Z  # Not a valid user. Skip 2025-11-03T15:48:18.0578518Z  continue 2025-11-03T15:48:18.0579280Z  2025-11-03T15:48:18.0579898Z  if user: 2025-11-03T15:48:18.0580774Z  usr_name = user.split(",")[0].strip("@") 2025-11-03T15:48:18.0582113Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-11-03T15:48:18.0583327Z  2025-11-03T15:48:18.0583964Z  return optins 2025-11-03T15:48:18.0584727Z  2025-11-03T15:48:18.0585321Z  2025-11-03T15:48:18.0586240Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-11-03T15:48:18.0587494Z  """ 2025-11-03T15:48:18.0588275Z  Check if the experiment name is valid. 2025-11-03T15:48:18.0589261Z  A valid name: 2025-11-03T15:48:18.0590549Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-11-03T15:48:18.0592368Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-11-03T15:48:18.0593743Z  - Cannot contain spaces 2025-11-03T15:48:18.0594640Z  """ 2025-11-03T15:48:18.0595281Z  2025-11-03T15:48:18.0596123Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-11-03T15:48:18.0597574Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-11-03T15:48:18.0598844Z  2025-11-03T15:48:18.0599473Z  if valid: 2025-11-03T15:48:18.0600217Z  return True 2025-11-03T15:48:18.0600986Z  2025-11-03T15:48:18.0601613Z  log.error( 2025-11-03T15:48:18.0604412Z  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-11-03T15:48:18.0607458Z  ) 2025-11-03T15:48:18.0608130Z  return False 2025-11-03T15:48:18.0608873Z  2025-11-03T15:48:18.0609481Z  2025-11-03T15:48:18.0610440Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-11-03T15:48:18.0611669Z  """ 2025-11-03T15:48:18.0612854Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-11-03T15:48:18.0614208Z  """ 2025-11-03T15:48:18.0614877Z  try: 2025-11-03T15:48:18.0615598Z  if settings_text: 2025-11-03T15:48:18.0617124Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-11-03T15:48:18.0618633Z  # for easy reading 2025-11-03T15:48:18.0620182Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-11-03T15:48:18.0621884Z  # the backtick character in shell commands. 2025-11-03T15:48:18.0623039Z  backtick = chr(96) # backtick character 2025-11-03T15:48:18.0624324Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-11-03T15:48:18.0625587Z  settings = load_yaml(settings_text) 2025-11-03T15:48:18.0626620Z  2025-11-03T15:48:18.0627740Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-11-03T15:48:18.0629320Z  experiments = {} 2025-11-03T15:48:18.0630178Z  2025-11-03T15:48:18.0631225Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-11-03T15:48:18.0632682Z  if not is_valid_experiment_name(exp_name): 2025-11-03T15:48:18.0634808Z  # 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-11-03T15:48:18.0636891Z  continue 2025-11-03T15:48:18.0637725Z  2025-11-03T15:48:18.0638387Z  valid_settings = {} 2025-11-03T15:48:18.0639389Z  for setting in exp_settings: 2025-11-03T15:48:18.0640463Z  if setting not in Experiment._fields: 2025-11-03T15:48:18.0641521Z  log.warning( 2025-11-03T15:48:18.0642883Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-11-03T15:48:18.0644205Z  ) 2025-11-03T15:48:18.0645033Z  else: 2025-11-03T15:48:18.0646054Z  valid_settings[setting] = exp_settings[setting] 2025-11-03T15:48:18.0647206Z  2025-11-03T15:48:18.0648076Z  experiments[exp_name] = Experiment(**valid_settings) 2025-11-03T15:48:18.0649276Z  return Settings(experiments) 2025-11-03T15:48:18.0650208Z  2025-11-03T15:48:18.0650850Z  except Exception: 2025-11-03T15:48:18.0651817Z  log.exception("Failed to parse settings") 2025-11-03T15:48:18.0652805Z  2025-11-03T15:48:18.0653447Z  return Settings() 2025-11-03T15:48:18.0654223Z  2025-11-03T15:48:18.0654822Z  2025-11-03T15:48:18.0655783Z def parse_settings(rollout_state: str) -> Settings: 2025-11-03T15:48:18.0656956Z  """ 2025-11-03T15:48:18.0657801Z  Parse settings, if any, from the rollout state. 2025-11-03T15:48:18.0658815Z  2025-11-03T15:48:18.0659811Z  If the issue body contains "---" then the text above that is the settings 2025-11-03T15:48:18.0661238Z  and the text below is the list of opted in users. 2025-11-03T15:48:18.0662265Z  2025-11-03T15:48:18.0663373Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-11-03T15:48:18.0664745Z  """ 2025-11-03T15:48:18.0665797Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:48:18.0667333Z  return parse_settings_from_text(settings_text) 2025-11-03T15:48:18.0668361Z  2025-11-03T15:48:18.0668955Z  2025-11-03T15:48:18.0669797Z def parse_users(rollout_state: str) -> UserOptins: 2025-11-03T15:48:18.0670859Z  """ 2025-11-03T15:48:18.0671606Z  Parse users from the rollout state. 2025-11-03T15:48:18.0672544Z  2025-11-03T15:48:18.0673142Z  """ 2025-11-03T15:48:18.0674163Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:48:18.0675561Z  return parse_user_opt_in_from_text(users_text) 2025-11-03T15:48:18.0676676Z  2025-11-03T15:48:18.0677267Z  2025-11-03T15:48:18.0678409Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:48:18.0679809Z  """ 2025-11-03T15:48:18.0680605Z  Check if a user is opted into an experiment 2025-11-03T15:48:18.0681612Z  """ 2025-11-03T15:48:18.0682491Z  return experiment_name in user_optins.get(user, []) 2025-11-03T15:48:18.0683575Z  2025-11-03T15:48:18.0684293Z  2025-11-03T15:48:18.0685461Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:48:18.0687119Z  """ 2025-11-03T15:48:18.0688009Z  Check if a user explicitly opted out of an experiment 2025-11-03T15:48:18.0689091Z  """ 2025-11-03T15:48:18.0690072Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-11-03T15:48:18.0691400Z  experiment_optout = "-" + experiment_name 2025-11-03T15:48:18.0692621Z  if experiment_optout not in user_optins.get(user, []): 2025-11-03T15:48:18.0693735Z  return False 2025-11-03T15:48:18.0694489Z  2025-11-03T15:48:18.0695328Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-11-03T15:48:18.0696548Z  log.warning( 2025-11-03T15:48:18.0698083Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-11-03T15:48:18.0699699Z  ) 2025-11-03T15:48:18.0700349Z  2025-11-03T15:48:18.0700984Z  return True 2025-11-03T15:48:18.0701704Z  2025-11-03T15:48:18.0702289Z  2025-11-03T15:48:18.0702925Z def get_runner_prefix( 2025-11-03T15:48:18.0703761Z  rollout_state: str, 2025-11-03T15:48:18.0704675Z  workflow_requestors: Iterable[str], 2025-11-03T15:48:18.0705621Z  branch: str, 2025-11-03T15:48:18.0706686Z  eligible_experiments: frozenset[str] = frozenset(), 2025-11-03T15:48:18.0707965Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-11-03T15:48:18.0709066Z  is_canary: bool = False, 2025-11-03T15:48:18.0709915Z ) -> str: 2025-11-03T15:48:18.0710718Z  settings = parse_settings(rollout_state) 2025-11-03T15:48:18.0711859Z  user_optins = parse_users(rollout_state) 2025-11-03T15:48:18.0712821Z  2025-11-03T15:48:18.0713591Z  fleet_prefix = "" 2025-11-03T15:48:18.0714402Z  prefixes = [] 2025-11-03T15:48:18.0715638Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-11-03T15:48:18.0717524Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-11-03T15:48:18.0718842Z  log.info( 2025-11-03T15:48:18.0720148Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-11-03T15:48:18.0721548Z  ) 2025-11-03T15:48:18.0722266Z  continue 2025-11-03T15:48:18.0723009Z  2025-11-03T15:48:18.0723667Z  if opt_out_experiments: 2025-11-03T15:48:18.0724688Z  if experiment_name in opt_out_experiments: 2025-11-03T15:48:18.0725882Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-11-03T15:48:18.0727071Z  log.info( 2025-11-03T15:48:18.0728839Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-11-03T15:48:18.0730689Z  ) 2025-11-03T15:48:18.0731428Z  continue 2025-11-03T15:48:18.0732220Z  2025-11-03T15:48:18.0732882Z  if eligible_experiments: 2025-11-03T15:48:18.0733949Z  if experiment_name not in eligible_experiments: 2025-11-03T15:48:18.0735153Z  exp_list = ", ".join(eligible_experiments) 2025-11-03T15:48:18.0736169Z  log.info( 2025-11-03T15:48:18.0737756Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-11-03T15:48:18.0739305Z  ) 2025-11-03T15:48:18.0740192Z  continue 2025-11-03T15:48:18.0741126Z  elif not experiment_settings.default: 2025-11-03T15:48:18.0742101Z  log.info( 2025-11-03T15:48:18.0743363Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-11-03T15:48:18.0744700Z  ) 2025-11-03T15:48:18.0745417Z  continue 2025-11-03T15:48:18.0746161Z  2025-11-03T15:48:18.0747121Z  # Is any workflow_requestor opted out to this experiment? 2025-11-03T15:48:18.0748268Z  opted_out_users = [ 2025-11-03T15:48:18.0749122Z  requestor 2025-11-03T15:48:18.0750025Z  for requestor in workflow_requestors 2025-11-03T15:48:18.0751280Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-11-03T15:48:18.0752442Z  ] 2025-11-03T15:48:18.0753096Z  2025-11-03T15:48:18.0753747Z  if opted_out_users: 2025-11-03T15:48:18.0754595Z  log.info( 2025-11-03T15:48:18.0755805Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-11-03T15:48:18.0757201Z  ) 2025-11-03T15:48:18.0757909Z  continue 2025-11-03T15:48:18.0758667Z  2025-11-03T15:48:18.0759502Z  # Is any workflow_requestor opted in to this experiment? 2025-11-03T15:48:18.0760640Z  opted_in_users = [ 2025-11-03T15:48:18.0761492Z  requestor 2025-11-03T15:48:18.0762379Z  for requestor in workflow_requestors 2025-11-03T15:48:18.0763641Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-11-03T15:48:18.0764775Z  ] 2025-11-03T15:48:18.0765432Z  2025-11-03T15:48:18.0766057Z  enabled = False 2025-11-03T15:48:18.0766986Z  if opted_in_users: 2025-11-03T15:48:18.0767942Z  log.info( 2025-11-03T15:48:18.0769125Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-11-03T15:48:18.0770394Z  ) 2025-11-03T15:48:18.0771122Z  enabled = True 2025-11-03T15:48:18.0771937Z  2025-11-03T15:48:18.0772662Z  elif experiment_settings.rollout_perc: 2025-11-03T15:48:18.0774212Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-11-03T15:48:18.0775968Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-11-03T15:48:18.0777274Z  log.info( 2025-11-03T15:48:18.0778921Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-11-03T15:48:18.0780582Z  ) 2025-11-03T15:48:18.0781361Z  enabled = True 2025-11-03T15:48:18.0782185Z  2025-11-03T15:48:18.0782802Z  if enabled: 2025-11-03T15:48:18.0783610Z  label = experiment_name 2025-11-03T15:48:18.0784641Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-11-03T15:48:18.0786172Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-11-03T15:48:18.0787881Z  # - If it's enabled, then we always list it's prefix first 2025-11-03T15:48:18.0789297Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-11-03T15:48:18.0790501Z  if is_canary: 2025-11-03T15:48:18.0791427Z  label += CANARY_FLEET_SUFFIX 2025-11-03T15:48:18.0792403Z  fleet_prefix = label 2025-11-03T15:48:18.0793292Z  else: 2025-11-03T15:48:18.0794244Z  prefixes.append(label) 2025-11-03T15:48:18.0795146Z  2025-11-03T15:48:18.0795777Z  if len(prefixes) > 1: 2025-11-03T15:48:18.0796700Z  log.error( 2025-11-03T15:48:18.0798659Z  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-11-03T15:48:18.0800709Z  ) 2025-11-03T15:48:18.0801609Z  prefixes = prefixes[:1] 2025-11-03T15:48:18.0802475Z  2025-11-03T15:48:18.0803123Z  # Fleet always comes first 2025-11-03T15:48:18.0804014Z  if fleet_prefix: 2025-11-03T15:48:18.0804872Z  prefixes.insert(0, fleet_prefix) 2025-11-03T15:48:18.0805793Z  2025-11-03T15:48:18.0806702Z  return ".".join(prefixes) + "." if prefixes else "" 2025-11-03T15:48:18.0807733Z  2025-11-03T15:48:18.0808331Z  2025-11-03T15:48:18.0809470Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-11-03T15:48:18.0810855Z  """ 2025-11-03T15:48:18.0811968Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-11-03T15:48:18.0813253Z  2025-11-03T15:48:18.0814298Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-11-03T15:48:18.0815582Z  """ 2025-11-03T15:48:18.0816308Z  gh = get_gh_client(github_token) 2025-11-03T15:48:18.0817410Z  issue = get_issue(gh, repo, issue_num) 2025-11-03T15:48:18.0818597Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-11-03T15:48:18.0819673Z  2025-11-03T15:48:18.0820272Z  2025-11-03T15:48:18.0821331Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-11-03T15:48:18.0822859Z  for _ in range(num_retries): 2025-11-03T15:48:18.0823741Z  try: 2025-11-03T15:48:18.0824533Z  req = Request(url=url, headers=headers) 2025-11-03T15:48:18.0825751Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-11-03T15:48:18.0827037Z  return json.loads(content) 2025-11-03T15:48:18.0827995Z  except Exception as e: 2025-11-03T15:48:18.0829016Z  log.warning(f"Could not download {url}: {e}") 2025-11-03T15:48:18.0830010Z  2025-11-03T15:48:18.0831045Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-11-03T15:48:18.0832342Z  return {} 2025-11-03T15:48:18.0833037Z  2025-11-03T15:48:18.0833620Z  2025-11-03T15:48:18.0834215Z @cache 2025-11-03T15:48:18.0835355Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-11-03T15:48:18.0836863Z  """ 2025-11-03T15:48:18.0837594Z  Dynamically get PR information 2025-11-03T15:48:18.0838479Z  """ 2025-11-03T15:48:18.0839408Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-11-03T15:48:18.0840545Z  headers = { 2025-11-03T15:48:18.0841423Z  "Accept": "application/vnd.github.v3+json", 2025-11-03T15:48:18.0842551Z  "Authorization": f"token {github_token}", 2025-11-03T15:48:18.0843523Z  } 2025-11-03T15:48:18.0844329Z  json_response: dict[str, Any] = download_json( 2025-11-03T15:48:18.0845443Z  url=f"{github_api}/issues/{pr_number}", 2025-11-03T15:48:18.0846431Z  headers=headers, 2025-11-03T15:48:18.0847341Z  ) 2025-11-03T15:48:18.0847972Z  2025-11-03T15:48:18.0848601Z  if not json_response: 2025-11-03T15:48:18.0849697Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-11-03T15:48:18.0850967Z  return {} 2025-11-03T15:48:18.0851704Z  2025-11-03T15:48:18.0852332Z  return json_response 2025-11-03T15:48:18.0853132Z  2025-11-03T15:48:18.0853717Z  2025-11-03T15:48:18.0854767Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-11-03T15:48:18.0856067Z  """ 2025-11-03T15:48:18.0857153Z  Dynamically get the latest list of labels from the pull request 2025-11-03T15:48:18.0858325Z  """ 2025-11-03T15:48:18.0859229Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-11-03T15:48:18.0860346Z  return { 2025-11-03T15:48:18.0861431Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-11-03T15:48:18.0862675Z  } 2025-11-03T15:48:18.0863306Z  2025-11-03T15:48:18.0863896Z  2025-11-03T15:48:18.0864519Z def main() -> None: 2025-11-03T15:48:18.0865312Z  args = parse_args() 2025-11-03T15:48:18.0866102Z  2025-11-03T15:48:18.0866945Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-11-03T15:48:18.0867922Z  2025-11-03T15:48:18.0868586Z  # Check if the PR is opt-out 2025-11-03T15:48:18.0869481Z  if args.pr_number: 2025-11-03T15:48:18.0870732Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-11-03T15:48:18.0872091Z  if OPT_OUT_LABEL in labels: 2025-11-03T15:48:18.0872985Z  log.info( 2025-11-03T15:48:18.0874286Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-11-03T15:48:18.0875666Z  ) 2025-11-03T15:48:18.0876804Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:48:18.0878023Z  sys.exit() 2025-11-03T15:48:18.0878921Z  2025-11-03T15:48:18.0879514Z  try: 2025-11-03T15:48:18.0880321Z  rollout_state = get_rollout_state_from_issue( 2025-11-03T15:48:18.0881625Z  args.github_token, args.github_issue_repo, args.github_issue 2025-11-03T15:48:18.0882788Z  ) 2025-11-03T15:48:18.0883438Z  2025-11-03T15:48:18.0884131Z  username = get_potential_pr_author( 2025-11-03T15:48:18.0885104Z  args.github_token, 2025-11-03T15:48:18.0885986Z  args.github_repo, 2025-11-03T15:48:18.0886967Z  args.github_actor, 2025-11-03T15:48:18.0887866Z  args.github_ref_type, 2025-11-03T15:48:18.0888780Z  args.github_branch, 2025-11-03T15:48:18.0889627Z  ) 2025-11-03T15:48:18.0890281Z  2025-11-03T15:48:18.0891138Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-11-03T15:48:18.0892221Z  2025-11-03T15:48:18.0892956Z  runner_label_prefix = get_runner_prefix( 2025-11-03T15:48:18.0893942Z  rollout_state, 2025-11-03T15:48:18.0894865Z  (args.github_issue_owner, username), 2025-11-03T15:48:18.0895850Z  args.github_branch, 2025-11-03T15:48:18.0896889Z  args.eligible_experiments, 2025-11-03T15:48:18.0897880Z  args.opt_out_experiments, 2025-11-03T15:48:18.0898785Z  is_canary, 2025-11-03T15:48:18.0899556Z  ) 2025-11-03T15:48:18.0900200Z  2025-11-03T15:48:18.0900850Z  except Exception as e: 2025-11-03T15:48:18.0901693Z  log.error( 2025-11-03T15:48:18.0902965Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-11-03T15:48:18.0904467Z  ) 2025-11-03T15:48:18.0905123Z  2025-11-03T15:48:18.0906061Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:48:18.0907319Z  2025-11-03T15:48:18.0907915Z  2025-11-03T15:48:18.0908541Z if __name__ == "__main__": 2025-11-03T15:48:18.0909362Z  main() 2025-11-03T15:48:18.0910023Z  2025-11-03T15:48:18.0910620Z EOF 2025-11-03T15:48:18.0911250Z  2025-11-03T15:48:18.0911917Z cat runner_determinator.py 2025-11-03T15:48:18.2080710Z shell: /usr/bin/bash -e {0} 2025-11-03T15:48:18.2081815Z env: 2025-11-03T15:48:18.2082851Z GITHUB_TOKEN: *** 2025-11-03T15:48:18.2083542Z ISSUE_NUMBER: 5132 2025-11-03T15:48:18.2084282Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:48:18.2085141Z ISSUE_OWNER: 2025-11-03T15:48:18.2085806Z CHECK_EXPERIMENTS: 2025-11-03T15:48:18.2086686Z OPT_OUT_EXPERIMENTS: 2025-11-03T15:48:18.2087420Z PR_NUMBER: 2025-11-03T15:48:18.2088070Z ##[endgroup] 2025-11-03T15:48:18.2294142Z # flake8: noqa: G004 2025-11-03T15:48:18.2294655Z 2025-11-03T15:48:18.2295386Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-11-03T15:48:18.2297349Z # must be kept in sync. You can do it easily by running the following command: 2025-11-03T15:48:18.2298775Z # python .github/scripts/update_runner_determinator.py 2025-11-03T15:48:18.2299540Z 2025-11-03T15:48:18.2299798Z """ 2025-11-03T15:48:18.2300751Z This runner determinator is used to determine which set of runners to run a 2025-11-03T15:48:18.2302273Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-11-03T15:48:18.2303819Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-11-03T15:48:18.2305212Z of which runners should be used to run which job. 2025-11-03T15:48:18.2305893Z 2025-11-03T15:48:18.2306861Z The configuration has two parts, the settings and a list of opted-in users, 2025-11-03T15:48:18.2308616Z separated by a line containing "---". If the line is not present, the 2025-11-03T15:48:18.2310122Z settings are considered to be empty with only the second part, the user 2025-11-03T15:48:18.2311292Z list, defined. 2025-11-03T15:48:18.2311666Z 2025-11-03T15:48:18.2312286Z The first part is a YAML block that defines the rollout settings. This can be 2025-11-03T15:48:18.2313879Z used to define any settings that are needed to determine which runners to use. 2025-11-03T15:48:18.2315285Z It's fields are defined by the RolloutSettings class below. 2025-11-03T15:48:18.2316043Z 2025-11-03T15:48:18.2316966Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-11-03T15:48:18.2318502Z The user list is also a comma separated list of additional features or 2025-11-03T15:48:18.2319764Z experiments which the user could be opted in to. 2025-11-03T15:48:18.2320458Z 2025-11-03T15:48:18.2320781Z The user list has the following rules: 2025-11-03T15:48:18.2321374Z 2025-11-03T15:48:18.2321902Z - Users are GitHub usernames, which must start with the @ prefix 2025-11-03T15:48:18.2323336Z - Each user is also a comma-separated list of features/experiments to enable 2025-11-03T15:48:18.2324632Z - A "#" prefix opts the user out of all experiments 2025-11-03T15:48:18.2325309Z 2025-11-03T15:48:18.2325587Z Example config: 2025-11-03T15:48:18.2326322Z # A list of experiments that can be opted into. 2025-11-03T15:48:18.2327677Z # This defines the behavior they'll induce when opted into. 2025-11-03T15:48:18.2328734Z # Expected syntax is: 2025-11-03T15:48:18.2329820Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-11-03T15:48:18.2331493Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-11-03T15:48:18.2332578Z 2025-11-03T15:48:18.2332873Z experiments: 2025-11-03T15:48:18.2333510Z lf: 2025-11-03T15:48:18.2334117Z rollout_percent: 25 2025-11-03T15:48:18.2335047Z all_branches: false 2025-11-03T15:48:18.2335792Z default: true 2025-11-03T15:48:18.2336630Z --- 2025-11-03T15:48:18.2337003Z 2025-11-03T15:48:18.2337266Z # Opt-ins: 2025-11-03T15:48:18.2338239Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-11-03T15:48:18.2339716Z # and specifying experiments to enable in a comma-separated list. 2025-11-03T15:48:18.2341056Z # To always opt out of an experiment, prefix it with a "-". 2025-11-03T15:48:18.2342155Z # Experiments should be from the above list. 2025-11-03T15:48:18.2342800Z 2025-11-03T15:48:18.2343088Z @User1,-lf,split_build 2025-11-03T15:48:18.2343811Z @User2,lf 2025-11-03T15:48:18.2344428Z @User3,split_build 2025-11-03T15:48:18.2345092Z """ 2025-11-03T15:48:18.2345398Z 2025-11-03T15:48:18.2345657Z import json 2025-11-03T15:48:18.2346262Z import logging 2025-11-03T15:48:18.2346990Z import os 2025-11-03T15:48:18.2347587Z import random 2025-11-03T15:48:18.2348197Z import re 2025-11-03T15:48:18.2348786Z import sys 2025-11-03T15:48:18.2349443Z from argparse import ArgumentParser 2025-11-03T15:48:18.2350322Z from collections.abc import Iterable 2025-11-03T15:48:18.2351189Z from functools import cache 2025-11-03T15:48:18.2351955Z from logging import LogRecord 2025-11-03T15:48:18.2352763Z from typing import Any, NamedTuple 2025-11-03T15:48:18.2353682Z from urllib.request import Request, urlopen 2025-11-03T15:48:18.2354334Z 2025-11-03T15:48:18.2354622Z import yaml 2025-11-03T15:48:18.2355302Z from github import Auth, Github 2025-11-03T15:48:18.2356159Z from github.Issue import Issue 2025-11-03T15:48:18.2356927Z 2025-11-03T15:48:18.2356934Z 2025-11-03T15:48:18.2357311Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-11-03T15:48:18.2358451Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-11-03T15:48:18.2359889Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-11-03T15:48:18.2360833Z 2025-11-03T15:48:18.2361210Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-11-03T15:48:18.2362318Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-11-03T15:48:18.2363172Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-11-03T15:48:18.2364087Z OPT_OUT_LABEL = "no-runner-experiments" 2025-11-03T15:48:18.2364682Z 2025-11-03T15:48:18.2365010Z SETTING_EXPERIMENTS = "experiments" 2025-11-03T15:48:18.2365570Z 2025-11-03T15:48:18.2365867Z LF_FLEET_EXPERIMENT = "lf" 2025-11-03T15:48:18.2366734Z CANARY_FLEET_SUFFIX = ".c" 2025-11-03T15:48:18.2367207Z 2025-11-03T15:48:18.2367215Z 2025-11-03T15:48:18.2367525Z class Experiment(NamedTuple): 2025-11-03T15:48:18.2368328Z rollout_perc: float = ( 2025-11-03T15:48:18.2369402Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-11-03T15:48:18.2370546Z ) 2025-11-03T15:48:18.2371140Z all_branches: bool = ( 2025-11-03T15:48:18.2372183Z False # If True, the experiment is also enabled on the exception branches 2025-11-03T15:48:18.2373322Z ) 2025-11-03T15:48:18.2373906Z default: bool = ( 2025-11-03T15:48:18.2374855Z True # If True, the experiment is enabled by default for all queries 2025-11-03T15:48:18.2375935Z ) 2025-11-03T15:48:18.2376253Z 2025-11-03T15:48:18.2376720Z # Add more fields as needed 2025-11-03T15:48:18.2377232Z 2025-11-03T15:48:18.2377239Z 2025-11-03T15:48:18.2377544Z class Settings(NamedTuple): 2025-11-03T15:48:18.2378263Z """ 2025-11-03T15:48:18.2379000Z Settings for the experiments that can be opted into. 2025-11-03T15:48:18.2379950Z """ 2025-11-03T15:48:18.2380264Z 2025-11-03T15:48:18.2380610Z experiments: dict[str, Experiment] = {} 2025-11-03T15:48:18.2381217Z 2025-11-03T15:48:18.2381224Z 2025-11-03T15:48:18.2381562Z class ColorFormatter(logging.Formatter): 2025-11-03T15:48:18.2382598Z """Color codes the log messages based on the log level""" 2025-11-03T15:48:18.2383326Z 2025-11-03T15:48:18.2383590Z COLORS = { 2025-11-03T15:48:18.2384229Z "WARNING": "\033[33m", # Yellow 2025-11-03T15:48:18.2385203Z "ERROR": "\033[31m", # Red 2025-11-03T15:48:18.2386016Z "CRITICAL": "\033[31m", # Red 2025-11-03T15:48:18.2387058Z "INFO": "\033[0m", # Reset 2025-11-03T15:48:18.2387852Z "DEBUG": "\033[0m", # Reset 2025-11-03T15:48:18.2388613Z } 2025-11-03T15:48:18.2388928Z 2025-11-03T15:48:18.2389287Z def format(self, record: LogRecord) -> str: 2025-11-03T15:48:18.2390542Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-11-03T15:48:18.2391843Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-11-03T15:48:18.2392797Z return super().format(record) 2025-11-03T15:48:18.2393349Z 2025-11-03T15:48:18.2393355Z 2025-11-03T15:48:18.2393679Z handler = logging.StreamHandler() 2025-11-03T15:48:18.2394853Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-11-03T15:48:18.2395793Z 2025-11-03T15:48:18.2396194Z log = logging.getLogger(os.path.basename(__file__)) 2025-11-03T15:48:18.2397427Z log.addHandler(handler) 2025-11-03T15:48:18.2398164Z log.setLevel(logging.INFO) 2025-11-03T15:48:18.2398631Z 2025-11-03T15:48:18.2398638Z 2025-11-03T15:48:18.2399043Z def set_github_output(key: str, value: str) -> None: 2025-11-03T15:48:18.2399972Z """ 2025-11-03T15:48:18.2400786Z Defines outputs of the github action that invokes this script 2025-11-03T15:48:18.2401852Z """ 2025-11-03T15:48:18.2402450Z if not GITHUB_OUTPUT: 2025-11-03T15:48:18.2404317Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-11-03T15:48:18.2406300Z log.warning( 2025-11-03T15:48:18.2407851Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-11-03T15:48:18.2409422Z ) 2025-11-03T15:48:18.2419494Z print(f"::set-output name={key}::{value}") 2025-11-03T15:48:18.2420470Z return 2025-11-03T15:48:18.2420869Z 2025-11-03T15:48:18.2421358Z with open(GITHUB_OUTPUT, "a") as f: 2025-11-03T15:48:18.2422329Z log.info(f"Setting output: {key}='{value}'") 2025-11-03T15:48:18.2423276Z f.write(f"{key}={value}\n") 2025-11-03T15:48:18.2423812Z 2025-11-03T15:48:18.2423818Z 2025-11-03T15:48:18.2424326Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-11-03T15:48:18.2425385Z return frozenset( 2025-11-03T15:48:18.2426411Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-11-03T15:48:18.2427715Z ) 2025-11-03T15:48:18.2428038Z 2025-11-03T15:48:18.2428046Z 2025-11-03T15:48:18.2428340Z def parse_args() -> Any: 2025-11-03T15:48:18.2429255Z parser = ArgumentParser("Get dynamic rollout settings") 2025-11-03T15:48:18.2430714Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-11-03T15:48:18.2432057Z parser.add_argument( 2025-11-03T15:48:18.2432799Z "--github-issue-repo", 2025-11-03T15:48:18.2433573Z type=str, 2025-11-03T15:48:18.2434222Z required=False, 2025-11-03T15:48:18.2434957Z default="pytorch/test-infra", 2025-11-03T15:48:18.2435827Z help="GitHub repo to get the issue", 2025-11-03T15:48:18.2436957Z ) 2025-11-03T15:48:18.2437581Z parser.add_argument( 2025-11-03T15:48:18.2438312Z "--github-repo", 2025-11-03T15:48:18.2438998Z type=str, 2025-11-03T15:48:18.2439646Z required=True, 2025-11-03T15:48:18.2440401Z help="GitHub repo where CI is running", 2025-11-03T15:48:18.2441261Z ) 2025-11-03T15:48:18.2441854Z parser.add_argument( 2025-11-03T15:48:18.2442853Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-11-03T15:48:18.2443963Z ) 2025-11-03T15:48:18.2444554Z parser.add_argument( 2025-11-03T15:48:18.2445596Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-11-03T15:48:18.2446947Z ) 2025-11-03T15:48:18.2447544Z parser.add_argument( 2025-11-03T15:48:18.2448809Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-11-03T15:48:18.2449977Z ) 2025-11-03T15:48:18.2450568Z parser.add_argument( 2025-11-03T15:48:18.2503957Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-11-03T15:48:18.2505460Z ) 2025-11-03T15:48:18.2506067Z parser.add_argument( 2025-11-03T15:48:18.2506954Z "--github-ref-type", 2025-11-03T15:48:18.2507702Z type=str, 2025-11-03T15:48:18.2508349Z required=True, 2025-11-03T15:48:18.2509136Z help="Current GitHub ref type, branch or tag", 2025-11-03T15:48:18.2510037Z ) 2025-11-03T15:48:18.2510635Z parser.add_argument( 2025-11-03T15:48:18.2511391Z "--eligible-experiments", 2025-11-03T15:48:18.2512238Z type=_str_comma_separated_to_set, 2025-11-03T15:48:18.2513097Z required=False, 2025-11-03T15:48:18.2513776Z default="", 2025-11-03T15:48:18.2515208Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-11-03T15:48:18.2516901Z ) 2025-11-03T15:48:18.2517489Z parser.add_argument( 2025-11-03T15:48:18.2518241Z "--opt-out-experiments", 2025-11-03T15:48:18.2519073Z type=_str_comma_separated_to_set, 2025-11-03T15:48:18.2519923Z required=False, 2025-11-03T15:48:18.2520596Z default="", 2025-11-03T15:48:18.2521219Z help=( 2025-11-03T15:48:18.2522326Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-11-03T15:48:18.2524275Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-11-03T15:48:18.2525739Z ), 2025-11-03T15:48:18.2526301Z ) 2025-11-03T15:48:18.2527178Z parser.add_argument( 2025-11-03T15:48:18.2527910Z "--pr-number", 2025-11-03T15:48:18.2528569Z type=str, 2025-11-03T15:48:18.2529206Z required=False, 2025-11-03T15:48:18.2529894Z default="", 2025-11-03T15:48:18.2530843Z help="the optional PR number where this is run", 2025-11-03T15:48:18.2531778Z ) 2025-11-03T15:48:18.2532105Z 2025-11-03T15:48:18.2532407Z return parser.parse_args() 2025-11-03T15:48:18.2532922Z 2025-11-03T15:48:18.2532928Z 2025-11-03T15:48:18.2533592Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-11-03T15:48:18.2534859Z auth = Auth.Token(github_token) 2025-11-03T15:48:18.2535694Z return Github(auth=auth) 2025-11-03T15:48:18.2536178Z 2025-11-03T15:48:18.2536184Z 2025-11-03T15:48:18.2537081Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-11-03T15:48:18.2538434Z repo = gh.get_repo(repo) 2025-11-03T15:48:18.2539251Z return repo.get_issue(number=issue_num) 2025-11-03T15:48:18.2539864Z 2025-11-03T15:48:18.2539870Z 2025-11-03T15:48:18.2540171Z def get_potential_pr_author( 2025-11-03T15:48:18.2541247Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-11-03T15:48:18.2542377Z ) -> str: 2025-11-03T15:48:18.2543220Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-11-03T15:48:18.2544566Z # Fetch the actual username from the original PR. The PR number is 2025-11-03T15:48:18.2545793Z # embedded in the tag name: ciflow// 2025-11-03T15:48:18.2546583Z 2025-11-03T15:48:18.2546894Z gh = get_gh_client(github_token) 2025-11-03T15:48:18.2547487Z 2025-11-03T15:48:18.2547915Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-11-03T15:48:18.2548946Z split_tag = ref_name.split("/") 2025-11-03T15:48:18.2549763Z if ( 2025-11-03T15:48:18.2550385Z len(split_tag) == 3 2025-11-03T15:48:18.2551158Z and split_tag[0] == "ciflow" 2025-11-03T15:48:18.2552025Z and split_tag[2].isnumeric() 2025-11-03T15:48:18.2552830Z ): 2025-11-03T15:48:18.2553451Z pr_number = split_tag[2] 2025-11-03T15:48:18.2554370Z try: 2025-11-03T15:48:18.2555066Z repository = gh.get_repo(repo) 2025-11-03T15:48:18.2556076Z pull = repository.get_pull(number=int(pr_number)) 2025-11-03T15:48:18.2557181Z except Exception as e: 2025-11-03T15:48:18.2558029Z raise Exception( # noqa: TRY002 2025-11-03T15:48:18.2559124Z f"issue with pull request {pr_number} from repo {repository}" 2025-11-03T15:48:18.2560197Z ) from e 2025-11-03T15:48:18.2561055Z return pull.user.login # type: ignore[no-any-return] 2025-11-03T15:48:18.2562217Z # In all other cases, return the original input username 2025-11-03T15:48:18.2563191Z return username 2025-11-03T15:48:18.2563580Z 2025-11-03T15:48:18.2563586Z 2025-11-03T15:48:18.2563947Z def is_exception_branch(branch: str) -> bool: 2025-11-03T15:48:18.2564826Z """ 2025-11-03T15:48:18.2565883Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-11-03T15:48:18.2567489Z """ 2025-11-03T15:48:18.2568382Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-11-03T15:48:18.2569266Z 2025-11-03T15:48:18.2569273Z 2025-11-03T15:48:18.2569591Z def load_yaml(yaml_text: str) -> Any: 2025-11-03T15:48:18.2570387Z try: 2025-11-03T15:48:18.2571009Z data = yaml.safe_load(yaml_text) 2025-11-03T15:48:18.2571839Z return data 2025-11-03T15:48:18.2572510Z except yaml.YAMLError: 2025-11-03T15:48:18.2573299Z log.exception("Error loading YAML") 2025-11-03T15:48:18.2574137Z raise 2025-11-03T15:48:18.2574486Z 2025-11-03T15:48:18.2574493Z 2025-11-03T15:48:18.2575173Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-11-03T15:48:18.2576394Z """ 2025-11-03T15:48:18.2577884Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-11-03T15:48:18.2578912Z 2025-11-03T15:48:18.2579641Z If the issue body contains "---" then the text above that is the settings 2025-11-03T15:48:18.2580910Z and the text below is the list of opted in users. 2025-11-03T15:48:18.2581577Z 2025-11-03T15:48:18.2582194Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-11-03T15:48:18.2583349Z """ 2025-11-03T15:48:18.2584059Z rollout_state_parts = rollout_state.split("---") 2025-11-03T15:48:18.2585033Z if len(rollout_state_parts) >= 2: 2025-11-03T15:48:18.2586044Z return rollout_state_parts[0], rollout_state_parts[1] 2025-11-03T15:48:18.2587190Z else: 2025-11-03T15:48:18.2587802Z return "", rollout_state 2025-11-03T15:48:18.2588302Z 2025-11-03T15:48:18.2588310Z 2025-11-03T15:48:18.2588638Z class UserOptins(dict[str, list[str]]): 2025-11-03T15:48:18.2589463Z """ 2025-11-03T15:48:18.2590309Z Dictionary of users with a list of features they have opted into 2025-11-03T15:48:18.2591364Z """ 2025-11-03T15:48:18.2591691Z 2025-11-03T15:48:18.2591698Z 2025-11-03T15:48:18.2592254Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-11-03T15:48:18.2593334Z """ 2025-11-03T15:48:18.2594518Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-11-03T15:48:18.2595701Z 2025-11-03T15:48:18.2596849Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-11-03T15:48:18.2598536Z - Example line: "@User1,lf,split_build" 2025-11-03T15:48:18.2599663Z - A "#" prefix indicates the user is opted out of all experiments 2025-11-03T15:48:18.2600467Z 2025-11-03T15:48:18.2600474Z 2025-11-03T15:48:18.2600722Z """ 2025-11-03T15:48:18.2601323Z optins = UserOptins() 2025-11-03T15:48:18.2602150Z for user in user_optin_text.split("\n"): 2025-11-03T15:48:18.2603053Z user = user.strip("\r\n\t -") 2025-11-03T15:48:18.2603932Z if not user or not user.startswith("@"): 2025-11-03T15:48:18.2604970Z # Not a valid user. Skip 2025-11-03T15:48:18.2605777Z continue 2025-11-03T15:48:18.2606167Z 2025-11-03T15:48:18.2606419Z if user: 2025-11-03T15:48:18.2607404Z usr_name = user.split(",")[0].strip("@") 2025-11-03T15:48:18.2608544Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-11-03T15:48:18.2609371Z 2025-11-03T15:48:18.2609645Z return optins 2025-11-03T15:48:18.2610023Z 2025-11-03T15:48:18.2610030Z 2025-11-03T15:48:18.2610493Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-11-03T15:48:18.2611480Z """ 2025-11-03T15:48:18.2612108Z Check if the experiment name is valid. 2025-11-03T15:48:18.2612936Z A valid name: 2025-11-03T15:48:18.2613970Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-11-03T15:48:18.2615531Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-11-03T15:48:18.2617417Z - Cannot contain spaces 2025-11-03T15:48:18.2618482Z """ 2025-11-03T15:48:18.2618803Z 2025-11-03T15:48:18.2619214Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-11-03T15:48:18.2620364Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-11-03T15:48:18.2621107Z 2025-11-03T15:48:18.2621358Z if valid: 2025-11-03T15:48:18.2621964Z return True 2025-11-03T15:48:18.2622341Z 2025-11-03T15:48:18.2622595Z log.error( 2025-11-03T15:48:18.2625044Z 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-11-03T15:48:18.2627910Z ) 2025-11-03T15:48:18.2628479Z return False 2025-11-03T15:48:18.2628857Z 2025-11-03T15:48:18.2628864Z 2025-11-03T15:48:18.2629353Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-11-03T15:48:18.2630365Z """ 2025-11-03T15:48:18.2631506Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-11-03T15:48:18.2632719Z """ 2025-11-03T15:48:18.2633271Z try: 2025-11-03T15:48:18.2633853Z if settings_text: 2025-11-03T15:48:18.2635033Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-11-03T15:48:18.2636355Z # for easy reading 2025-11-03T15:48:18.2637770Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-11-03T15:48:18.2639237Z # the backtick character in shell commands. 2025-11-03T15:48:18.2640206Z backtick = chr(96) # backtick character 2025-11-03T15:48:18.2641273Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-11-03T15:48:18.2642341Z settings = load_yaml(settings_text) 2025-11-03T15:48:18.2642943Z 2025-11-03T15:48:18.2643616Z # For now we just load experiments. We can expand this if/when we add more settings 2025-11-03T15:48:18.2644862Z experiments = {} 2025-11-03T15:48:18.2645329Z 2025-11-03T15:48:18.2645934Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-11-03T15:48:18.2647272Z if not is_valid_experiment_name(exp_name): 2025-11-03T15:48:18.2649108Z # 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-11-03T15:48:18.2650888Z continue 2025-11-03T15:48:18.2651355Z 2025-11-03T15:48:18.2651642Z valid_settings = {} 2025-11-03T15:48:18.2652469Z for setting in exp_settings: 2025-11-03T15:48:18.2653382Z if setting not in Experiment._fields: 2025-11-03T15:48:18.2654272Z log.warning( 2025-11-03T15:48:18.2655423Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-11-03T15:48:18.2656892Z ) 2025-11-03T15:48:18.2657578Z else: 2025-11-03T15:48:18.2658395Z valid_settings[setting] = exp_settings[setting] 2025-11-03T15:48:18.2659087Z 2025-11-03T15:48:18.2659524Z experiments[exp_name] = Experiment(**valid_settings) 2025-11-03T15:48:18.2660550Z return Settings(experiments) 2025-11-03T15:48:18.2661124Z 2025-11-03T15:48:18.2661400Z except Exception: 2025-11-03T15:48:18.2662391Z log.exception("Failed to parse settings") 2025-11-03T15:48:18.2663053Z 2025-11-03T15:48:18.2663335Z return Settings() 2025-11-03T15:48:18.2663743Z 2025-11-03T15:48:18.2663749Z 2025-11-03T15:48:18.2664152Z def parse_settings(rollout_state: str) -> Settings: 2025-11-03T15:48:18.2665079Z """ 2025-11-03T15:48:18.2665807Z Parse settings, if any, from the rollout state. 2025-11-03T15:48:18.2666754Z 2025-11-03T15:48:18.2667418Z If the issue body contains "---" then the text above that is the settings 2025-11-03T15:48:18.2668987Z and the text below is the list of opted in users. 2025-11-03T15:48:18.2669654Z 2025-11-03T15:48:18.2670356Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-11-03T15:48:18.2671560Z """ 2025-11-03T15:48:18.2672454Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:48:18.2673689Z return parse_settings_from_text(settings_text) 2025-11-03T15:48:18.2674366Z 2025-11-03T15:48:18.2674373Z 2025-11-03T15:48:18.2674764Z def parse_users(rollout_state: str) -> UserOptins: 2025-11-03T15:48:18.2675678Z """ 2025-11-03T15:48:18.2676291Z Parse users from the rollout state. 2025-11-03T15:48:18.2676972Z 2025-11-03T15:48:18.2677241Z """ 2025-11-03T15:48:18.2678086Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:48:18.2679288Z return parse_user_opt_in_from_text(users_text) 2025-11-03T15:48:18.2679949Z 2025-11-03T15:48:18.2679956Z 2025-11-03T15:48:18.2680789Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:48:18.2682024Z """ 2025-11-03T15:48:18.2682681Z Check if a user is opted into an experiment 2025-11-03T15:48:18.2683541Z """ 2025-11-03T15:48:18.2684258Z return experiment_name in user_optins.get(user, []) 2025-11-03T15:48:18.2684953Z 2025-11-03T15:48:18.2684959Z 2025-11-03T15:48:18.2685644Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:48:18.2686995Z """ 2025-11-03T15:48:18.2687738Z Check if a user explicitly opted out of an experiment 2025-11-03T15:48:18.2688669Z """ 2025-11-03T15:48:18.2689460Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-11-03T15:48:18.2690624Z experiment_optout = "-" + experiment_name 2025-11-03T15:48:18.2691659Z if experiment_optout not in user_optins.get(user, []): 2025-11-03T15:48:18.2692648Z return False 2025-11-03T15:48:18.2693041Z 2025-11-03T15:48:18.2693499Z if is_user_opted_in(user, user_optins, experiment_name): 2025-11-03T15:48:18.2694486Z log.warning( 2025-11-03T15:48:18.2695812Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-11-03T15:48:18.2697369Z ) 2025-11-03T15:48:18.2697700Z 2025-11-03T15:48:18.2697955Z return True 2025-11-03T15:48:18.2698314Z 2025-11-03T15:48:18.2698320Z 2025-11-03T15:48:18.2698599Z def get_runner_prefix( 2025-11-03T15:48:18.2699289Z rollout_state: str, 2025-11-03T15:48:18.2700025Z workflow_requestors: Iterable[str], 2025-11-03T15:48:18.2700853Z branch: str, 2025-11-03T15:48:18.2701671Z eligible_experiments: frozenset[str] = frozenset(), 2025-11-03T15:48:18.2702756Z opt_out_experiments: frozenset[str] = frozenset(), 2025-11-03T15:48:18.2703727Z is_canary: bool = False, 2025-11-03T15:48:18.2704470Z ) -> str: 2025-11-03T15:48:18.2705275Z settings = parse_settings(rollout_state) 2025-11-03T15:48:18.2706235Z user_optins = parse_users(rollout_state) 2025-11-03T15:48:18.2707070Z 2025-11-03T15:48:18.2707351Z fleet_prefix = "" 2025-11-03T15:48:18.2708035Z prefixes = [] 2025-11-03T15:48:18.2709048Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-11-03T15:48:18.2710589Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-11-03T15:48:18.2711740Z log.info( 2025-11-03T15:48:18.2712824Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-11-03T15:48:18.2714068Z ) 2025-11-03T15:48:18.2714655Z continue 2025-11-03T15:48:18.2715040Z 2025-11-03T15:48:18.2715335Z if opt_out_experiments: 2025-11-03T15:48:18.2716175Z if experiment_name in opt_out_experiments: 2025-11-03T15:48:18.2717364Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-11-03T15:48:18.2718305Z log.info( 2025-11-03T15:48:18.2719835Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-11-03T15:48:18.2721509Z ) 2025-11-03T15:48:18.2722129Z continue 2025-11-03T15:48:18.2722555Z 2025-11-03T15:48:18.2722851Z if eligible_experiments: 2025-11-03T15:48:18.2723738Z if experiment_name not in eligible_experiments: 2025-11-03T15:48:18.2724786Z exp_list = ", ".join(eligible_experiments) 2025-11-03T15:48:18.2725687Z log.info( 2025-11-03T15:48:18.2727085Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-11-03T15:48:18.2728477Z ) 2025-11-03T15:48:18.2729089Z continue 2025-11-03T15:48:18.2729838Z elif not experiment_settings.default: 2025-11-03T15:48:18.2730706Z log.info( 2025-11-03T15:48:18.2731903Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-11-03T15:48:18.2733123Z ) 2025-11-03T15:48:18.2733718Z continue 2025-11-03T15:48:18.2734106Z 2025-11-03T15:48:18.2734542Z # Is any workflow_requestor opted out to this experiment? 2025-11-03T15:48:18.2735921Z opted_out_users = [ 2025-11-03T15:48:18.2737021Z requestor 2025-11-03T15:48:18.2737741Z for requestor in workflow_requestors 2025-11-03T15:48:18.2738854Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-11-03T15:48:18.2739867Z ] 2025-11-03T15:48:18.2740192Z 2025-11-03T15:48:18.2740470Z if opted_out_users: 2025-11-03T15:48:18.2741182Z log.info( 2025-11-03T15:48:18.2742170Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-11-03T15:48:18.2743312Z ) 2025-11-03T15:48:18.2743922Z continue 2025-11-03T15:48:18.2744322Z 2025-11-03T15:48:18.2744757Z # Is any workflow_requestor opted in to this experiment? 2025-11-03T15:48:18.2745749Z opted_in_users = [ 2025-11-03T15:48:18.2746602Z requestor 2025-11-03T15:48:18.2747382Z for requestor in workflow_requestors 2025-11-03T15:48:18.2748456Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-11-03T15:48:18.2749476Z ] 2025-11-03T15:48:18.2749802Z 2025-11-03T15:48:18.2750066Z enabled = False 2025-11-03T15:48:18.2750750Z if opted_in_users: 2025-11-03T15:48:18.2751452Z log.info( 2025-11-03T15:48:18.2752419Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-11-03T15:48:18.2753541Z ) 2025-11-03T15:48:18.2754153Z enabled = True 2025-11-03T15:48:18.2754600Z 2025-11-03T15:48:18.2754939Z elif experiment_settings.rollout_perc: 2025-11-03T15:48:18.2756307Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-11-03T15:48:18.2758195Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-11-03T15:48:18.2759282Z log.info( 2025-11-03T15:48:18.2760679Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-11-03T15:48:18.2762200Z ) 2025-11-03T15:48:18.2762839Z enabled = True 2025-11-03T15:48:18.2763331Z 2025-11-03T15:48:18.2763589Z if enabled: 2025-11-03T15:48:18.2764252Z label = experiment_name 2025-11-03T15:48:18.2765122Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-11-03T15:48:18.2766569Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-11-03T15:48:18.2768030Z # - If it's enabled, then we always list it's prefix first 2025-11-03T15:48:18.2769278Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-11-03T15:48:18.2770388Z if is_canary: 2025-11-03T15:48:18.2771155Z label += CANARY_FLEET_SUFFIX 2025-11-03T15:48:18.2772031Z fleet_prefix = label 2025-11-03T15:48:18.2772803Z else: 2025-11-03T15:48:18.2773472Z prefixes.append(label) 2025-11-03T15:48:18.2774036Z 2025-11-03T15:48:18.2774317Z if len(prefixes) > 1: 2025-11-03T15:48:18.2775024Z log.error( 2025-11-03T15:48:18.2776843Z 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-11-03T15:48:18.2778803Z ) 2025-11-03T15:48:18.2779414Z prefixes = prefixes[:1] 2025-11-03T15:48:18.2779914Z 2025-11-03T15:48:18.2780199Z # Fleet always comes first 2025-11-03T15:48:18.2780956Z if fleet_prefix: 2025-11-03T15:48:18.2781665Z prefixes.insert(0, fleet_prefix) 2025-11-03T15:48:18.2782291Z 2025-11-03T15:48:18.2782814Z return ".".join(prefixes) + "." if prefixes else "" 2025-11-03T15:48:18.2783499Z 2025-11-03T15:48:18.2783507Z 2025-11-03T15:48:18.2784219Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-11-03T15:48:18.2785494Z """ 2025-11-03T15:48:18.2786431Z Gets the first comment of the issue, which contains the desired rollout state. 2025-11-03T15:48:18.2787578Z 2025-11-03T15:48:18.2788205Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-11-03T15:48:18.2788460Z """ 2025-11-03T15:48:18.2788756Z gh = get_gh_client(github_token) 2025-11-03T15:48:18.2789078Z issue = get_issue(gh, repo, issue_num) 2025-11-03T15:48:18.2789515Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-11-03T15:48:18.2789522Z 2025-11-03T15:48:18.2789528Z 2025-11-03T15:48:18.2790172Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-11-03T15:48:18.2790470Z for _ in range(num_retries): 2025-11-03T15:48:18.2790726Z try: 2025-11-03T15:48:18.2791064Z req = Request(url=url, headers=headers) 2025-11-03T15:48:18.2791516Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-11-03T15:48:18.2791817Z return json.loads(content) 2025-11-03T15:48:18.2792115Z except Exception as e: 2025-11-03T15:48:18.2792487Z log.warning(f"Could not download {url}: {e}") 2025-11-03T15:48:18.2792494Z 2025-11-03T15:48:18.2793125Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-11-03T15:48:18.2793387Z return {} 2025-11-03T15:48:18.2793394Z 2025-11-03T15:48:18.2793401Z 2025-11-03T15:48:18.2793640Z @cache 2025-11-03T15:48:18.2794320Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-11-03T15:48:18.2794568Z """ 2025-11-03T15:48:18.2794874Z Dynamically get PR information 2025-11-03T15:48:18.2795260Z """ 2025-11-03T15:48:18.2795738Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-11-03T15:48:18.2796003Z headers = { 2025-11-03T15:48:18.2796357Z "Accept": "application/vnd.github.v3+json", 2025-11-03T15:48:18.2796851Z "Authorization": f"token {github_token}", 2025-11-03T15:48:18.2797121Z } 2025-11-03T15:48:18.2797480Z json_response: dict[str, Any] = download_json( 2025-11-03T15:48:18.2797812Z url=f"{github_api}/issues/{pr_number}", 2025-11-03T15:48:18.2798081Z headers=headers, 2025-11-03T15:48:18.2798347Z ) 2025-11-03T15:48:18.2798355Z 2025-11-03T15:48:18.2798635Z if not json_response: 2025-11-03T15:48:18.2799107Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-11-03T15:48:18.2799370Z return {} 2025-11-03T15:48:18.2799377Z 2025-11-03T15:48:18.2799651Z return json_response 2025-11-03T15:48:18.2799658Z 2025-11-03T15:48:18.2799666Z 2025-11-03T15:48:18.2800305Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-11-03T15:48:18.2800570Z """ 2025-11-03T15:48:18.2801084Z Dynamically get the latest list of labels from the pull request 2025-11-03T15:48:18.2801333Z """ 2025-11-03T15:48:18.2801799Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-11-03T15:48:18.2802058Z return { 2025-11-03T15:48:18.2802808Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-11-03T15:48:18.2803066Z } 2025-11-03T15:48:18.2803074Z 2025-11-03T15:48:18.2803080Z 2025-11-03T15:48:18.2803357Z def main() -> None: 2025-11-03T15:48:18.2803628Z args = parse_args() 2025-11-03T15:48:18.2803635Z 2025-11-03T15:48:18.2803975Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-11-03T15:48:18.2803983Z 2025-11-03T15:48:18.2804273Z # Check if the PR is opt-out 2025-11-03T15:48:18.2804539Z if args.pr_number: 2025-11-03T15:48:18.2805184Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-11-03T15:48:18.2805633Z if OPT_OUT_LABEL in labels: 2025-11-03T15:48:18.2805896Z log.info( 2025-11-03T15:48:18.2806803Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-11-03T15:48:18.2807237Z ) 2025-11-03T15:48:18.2808732Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:48:18.2810644Z sys.exit() 2025-11-03T15:48:18.2811344Z 2025-11-03T15:48:18.2811750Z try: 2025-11-03T15:48:18.2812881Z rollout_state = get_rollout_state_from_issue( 2025-11-03T15:48:18.2815173Z args.github_token, args.github_issue_repo, args.github_issue 2025-11-03T15:48:18.2817346Z ) 2025-11-03T15:48:18.2818302Z 2025-11-03T15:48:18.2819026Z username = get_potential_pr_author( 2025-11-03T15:48:18.2820665Z args.github_token, 2025-11-03T15:48:18.2822001Z args.github_repo, 2025-11-03T15:48:18.2823441Z args.github_actor, 2025-11-03T15:48:18.2824778Z args.github_ref_type, 2025-11-03T15:48:18.2826185Z args.github_branch, 2025-11-03T15:48:18.2827687Z ) 2025-11-03T15:48:18.2828276Z 2025-11-03T15:48:18.2829042Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-11-03T15:48:18.2830351Z 2025-11-03T15:48:18.2830968Z runner_label_prefix = get_runner_prefix( 2025-11-03T15:48:18.2832599Z rollout_state, 2025-11-03T15:48:18.2833876Z (args.github_issue_owner, username), 2025-11-03T15:48:18.2835279Z args.github_branch, 2025-11-03T15:48:18.2836073Z args.eligible_experiments, 2025-11-03T15:48:18.2837208Z args.opt_out_experiments, 2025-11-03T15:48:18.2838020Z is_canary, 2025-11-03T15:48:18.2838679Z ) 2025-11-03T15:48:18.2838999Z 2025-11-03T15:48:18.2839293Z except Exception as e: 2025-11-03T15:48:18.2840006Z log.error( 2025-11-03T15:48:18.2841102Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-11-03T15:48:18.2842532Z ) 2025-11-03T15:48:18.2842863Z 2025-11-03T15:48:18.2843380Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:48:18.2844175Z 2025-11-03T15:48:18.2844183Z 2025-11-03T15:48:18.2844454Z if __name__ == "__main__": 2025-11-03T15:48:18.2845142Z main() 2025-11-03T15:48:18.2845463Z 2025-11-03T15:48:18.2948695Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-11-03T15:48:18.2950076Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-11-03T15:48:18.2983818Z shell: /usr/bin/bash -e {0} 2025-11-03T15:48:18.2984556Z env: 2025-11-03T15:48:18.2985434Z GITHUB_TOKEN: *** 2025-11-03T15:48:18.2986083Z ISSUE_NUMBER: 5132 2025-11-03T15:48:18.2987025Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:48:18.2987822Z ISSUE_OWNER: 2025-11-03T15:48:18.2988440Z CHECK_EXPERIMENTS: 2025-11-03T15:48:18.2989107Z OPT_OUT_EXPERIMENTS: 2025-11-03T15:48:18.2989792Z PR_NUMBER: 2025-11-03T15:48:18.2990374Z ##[endgroup] 2025-11-03T15:48:20.9318461Z Defaulting to user installation because normal site-packages is not writeable 2025-11-03T15:48:22.1329850Z Collecting urllib3==1.26.18 2025-11-03T15:48:22.1656698Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-11-03T15:48:22.1867002Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.0 MB/s eta 0:00:00 2025-11-03T15:48:22.2091388Z Collecting PyGithub==2.3.0 2025-11-03T15:48:22.2127015Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-11-03T15:48:22.2555112Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-11-03T15:48:22.2589869Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-11-03T15:48:22.2632438Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-11-03T15:48:22.2648494Z 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-11-03T15:48:22.2662857Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-11-03T15:48:22.2902957Z Collecting Deprecated (from PyGithub==2.3.0) 2025-11-03T15:48:22.2930506Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-11-03T15:48:22.3155367Z 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-11-03T15:48:22.4335024Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-11-03T15:48:22.4366284Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-11-03T15:48:22.5887928Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-11-03T15:48:22.5918247Z Downloading wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (8.8 kB) 2025-11-03T15:48:22.6119860Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-11-03T15:48:22.6146965Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-11-03T15:48:22.6399356Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-11-03T15:48:22.6452691Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 43.1 MB/s eta 0:00:00 2025-11-03T15:48:22.6481308Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-11-03T15:48:22.6564060Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 68.9 MB/s eta 0:00:00 2025-11-03T15:48:22.6591316Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-11-03T15:48:22.6703725Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 144.4 MB/s eta 0:00:00 2025-11-03T15:48:22.6750250Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-11-03T15:48:22.6812361Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-11-03T15:48:22.6861889Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 64.8 MB/s eta 0:00:00 2025-11-03T15:48:22.6893504Z Downloading wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (121 kB) 2025-11-03T15:48:22.6936182Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.4/121.4 kB 42.9 MB/s eta 0:00:00 2025-11-03T15:48:22.6964083Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-11-03T15:48:22.7006763Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 40.4 MB/s eta 0:00:00 2025-11-03T15:48:23.0533419Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-11-03T15:48:23.5904739Z Successfully installed Deprecated-1.3.1 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.0 urllib3-1.26.18 wrapt-2.0.0 2025-11-03T15:48:23.6717064Z ##[group]Run curr_branch="main" 2025-11-03T15:48:23.6717389Z curr_branch="main" 2025-11-03T15:48:23.6717626Z curr_ref_type="branch" 2025-11-03T15:48:23.6717888Z echo "Current branch is '$curr_branch'" 2025-11-03T15:48:23.6718171Z  2025-11-03T15:48:23.6718357Z python3 runner_determinator.py \ 2025-11-03T15:48:23.6718646Z  --github-token "$GITHUB_TOKEN" \ 2025-11-03T15:48:23.6718925Z  --github-issue "$ISSUE_NUMBER" \ 2025-11-03T15:48:23.6719235Z  --github-branch "$curr_branch" \ 2025-11-03T15:48:23.6719497Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-11-03T15:48:23.6719807Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-11-03T15:48:23.6720089Z  --github-ref-type "$curr_ref_type" \ 2025-11-03T15:48:23.6720355Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-11-03T15:48:23.6720655Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-11-03T15:48:23.6721012Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-11-03T15:48:23.6721299Z  --pr-number "${PR_NUMBER}" 2025-11-03T15:48:23.6755153Z shell: /usr/bin/bash -e {0} 2025-11-03T15:48:23.6755372Z env: 2025-11-03T15:48:23.6755801Z GITHUB_TOKEN: *** 2025-11-03T15:48:23.6755988Z ISSUE_NUMBER: 5132 2025-11-03T15:48:23.6756194Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:48:23.6756423Z ISSUE_OWNER: 2025-11-03T15:48:23.6756859Z CHECK_EXPERIMENTS: 2025-11-03T15:48:23.6757049Z OPT_OUT_EXPERIMENTS: 2025-11-03T15:48:23.6757228Z PR_NUMBER: 2025-11-03T15:48:23.6757385Z ##[endgroup] 2025-11-03T15:48:23.6809860Z Current branch is 'main' 2025-11-03T15:48:25.4885074Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-11-03T15:48:25.4886006Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-11-03T15:48:25.4887257Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-11-03T15:48:25.4888101Z INFO : Setting output: label-type='' 2025-11-03T15:48:25.5216085Z Evaluate and set job outputs 2025-11-03T15:48:25.5223234Z Cleaning up orphan processes