2025-08-26T19:31:44.0477355Z Current runner version: '2.328.0' 2025-08-26T19:31:44.0501883Z ##[group]Runner Image Provisioner 2025-08-26T19:31:44.0502728Z Hosted Compute Agent 2025-08-26T19:31:44.0503450Z Version: 20250825.382 2025-08-26T19:31:44.0504175Z Commit: 7109f2abf901479dac39397456e363ac0cca0730 2025-08-26T19:31:44.0504915Z Build Date: 2025-08-25T22:55:50Z 2025-08-26T19:31:44.0505588Z ##[endgroup] 2025-08-26T19:31:44.0506281Z ##[group]Operating System 2025-08-26T19:31:44.0506880Z Ubuntu 2025-08-26T19:31:44.0507414Z 24.04.2 2025-08-26T19:31:44.0507933Z LTS 2025-08-26T19:31:44.0508455Z ##[endgroup] 2025-08-26T19:31:44.0508967Z ##[group]Runner Image 2025-08-26T19:31:44.0509606Z Image: ubuntu-24.04 2025-08-26T19:31:44.0510189Z Version: 20250818.1.0 2025-08-26T19:31:44.0511402Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250818.1/images/ubuntu/Ubuntu2404-Readme.md 2025-08-26T19:31:44.0513373Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250818.1 2025-08-26T19:31:44.0514502Z ##[endgroup] 2025-08-26T19:31:44.0515601Z ##[group]GITHUB_TOKEN Permissions 2025-08-26T19:31:44.0517589Z Metadata: read 2025-08-26T19:31:44.0518147Z ##[endgroup] 2025-08-26T19:31:44.0520402Z Secret source: Actions 2025-08-26T19:31:44.0521085Z Prepare workflow directory 2025-08-26T19:31:44.1032331Z Prepare all required actions 2025-08-26T19:31:44.1087740Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (262640fd220236042fbf4443cc163c8838c84c3d) 2025-08-26T19:31:44.1092656Z ##[group] Inputs 2025-08-26T19:31:44.1093437Z check_experiments: 2025-08-26T19:31:44.1094035Z opt_out_experiments: 2025-08-26T19:31:44.1094563Z triggering_actor: pytorchmergebot 2025-08-26T19:31:44.1095271Z issue_owner: 2025-08-26T19:31:44.1095738Z curr_branch: main 2025-08-26T19:31:44.1096248Z curr_ref_type: branch 2025-08-26T19:31:44.1096850Z issue_number: 5132 2025-08-26T19:31:44.1097488Z ##[endgroup] 2025-08-26T19:31:44.1098094Z Complete job name: get-label-type / runner-determinator 2025-08-26T19:31:44.8321444Z ##[group]Run cat < runner_determinator.py 2025-08-26T19:31:44.8324205Z cat < runner_determinator.py 2025-08-26T19:31:44.8324955Z # flake8: noqa: G004 2025-08-26T19:31:44.8325563Z  2025-08-26T19:31:44.8326487Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:31:44.8327763Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:31:44.8328883Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:31:44.8329779Z  2025-08-26T19:31:44.8330310Z """ 2025-08-26T19:31:44.8331165Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:31:44.8332526Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:31:44.8334254Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:31:44.8335571Z of which runners should be used to run which job. 2025-08-26T19:31:44.8336548Z  2025-08-26T19:31:44.8337395Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:31:44.8338786Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:31:44.8340198Z settings are considered to be empty with only the second part, the user 2025-08-26T19:31:44.8341373Z list, defined. 2025-08-26T19:31:44.8342069Z  2025-08-26T19:31:44.8343149Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:31:44.8344763Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:31:44.8346086Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:31:44.8347157Z  2025-08-26T19:31:44.8348125Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:31:44.8349738Z The user list is also a comma separated list of additional features or 2025-08-26T19:31:44.8351053Z experiments which the user could be opted in to. 2025-08-26T19:31:44.8351940Z  2025-08-26T19:31:44.8352583Z The user list has the following rules: 2025-08-26T19:31:44.8353654Z  2025-08-26T19:31:44.8354574Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:31:44.8355943Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:31:44.8357201Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:31:44.8358022Z  2025-08-26T19:31:44.8358537Z Example config: 2025-08-26T19:31:44.8359370Z  # A list of experiments that can be opted into. 2025-08-26T19:31:44.8360382Z  # This defines the behavior they'll induce when opted into. 2025-08-26T19:31:44.8361298Z  # Expected syntax is: 2025-08-26T19:31:44.8362356Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:31:44.8363869Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:31:44.8364991Z  2025-08-26T19:31:44.8457181Z  experiments: 2025-08-26T19:31:44.8457841Z  lf: 2025-08-26T19:31:44.8458457Z  rollout_percent: 25 2025-08-26T19:31:44.8459166Z  all_branches: false 2025-08-26T19:31:44.8459855Z  default: true 2025-08-26T19:31:44.8460475Z  --- 2025-08-26T19:31:44.8460996Z  2025-08-26T19:31:44.8461496Z  # Opt-ins: 2025-08-26T19:31:44.8462531Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:31:44.8464490Z  # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:31:44.8465713Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:31:44.8466717Z  # Experiments should be from the above list. 2025-08-26T19:31:44.8467516Z  2025-08-26T19:31:44.8468049Z  @User1,-lf,split_build 2025-08-26T19:31:44.8468747Z  @User2,lf 2025-08-26T19:31:44.8469355Z  @User3,split_build 2025-08-26T19:31:44.8470010Z """ 2025-08-26T19:31:44.8470519Z  2025-08-26T19:31:44.8470966Z import json 2025-08-26T19:31:44.8471445Z import logging 2025-08-26T19:31:44.8471952Z import os 2025-08-26T19:31:44.8472419Z import random 2025-08-26T19:31:44.8472917Z import re 2025-08-26T19:31:44.8473727Z import sys 2025-08-26T19:31:44.8474275Z from argparse import ArgumentParser 2025-08-26T19:31:44.8475041Z from collections.abc import Iterable 2025-08-26T19:31:44.8475712Z from functools import cache 2025-08-26T19:31:44.8476327Z from logging import LogRecord 2025-08-26T19:31:44.8476973Z from typing import Any, NamedTuple 2025-08-26T19:31:44.8477682Z from urllib.request import Request, urlopen 2025-08-26T19:31:44.8478352Z  2025-08-26T19:31:44.8478771Z import yaml 2025-08-26T19:31:44.8479284Z from github import Auth, Github 2025-08-26T19:31:44.8479917Z from github.Issue import Issue 2025-08-26T19:31:44.8480509Z  2025-08-26T19:31:44.8480893Z  2025-08-26T19:31:44.8481382Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:31:44.8482226Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:31:44.8483541Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:31:44.8484396Z  2025-08-26T19:31:44.8484904Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:31:44.8485789Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:31:44.8486430Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:31:44.8487139Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:31:44.8487795Z  2025-08-26T19:31:44.8488262Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:31:44.8488851Z  2025-08-26T19:31:44.8489267Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:31:44.8489841Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:31:44.8490374Z  2025-08-26T19:31:44.8490751Z  2025-08-26T19:31:44.8491161Z class Experiment(NamedTuple): 2025-08-26T19:31:44.8491734Z  rollout_perc: float = ( 2025-08-26T19:31:44.8492506Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:31:44.8493382Z  ) 2025-08-26T19:31:44.8493828Z  all_branches: bool = ( 2025-08-26T19:31:44.8494588Z  False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:31:44.8495357Z  ) 2025-08-26T19:31:44.8495777Z  default: bool = ( 2025-08-26T19:31:44.8496457Z  True # If True, the experiment is enabled by default for all queries 2025-08-26T19:31:44.8497181Z  ) 2025-08-26T19:31:44.8497576Z  2025-08-26T19:31:44.8497989Z  # Add more fields as needed 2025-08-26T19:31:44.8498519Z  2025-08-26T19:31:44.8498894Z  2025-08-26T19:31:44.8499299Z class Settings(NamedTuple): 2025-08-26T19:31:44.8499832Z  """ 2025-08-26T19:31:44.8500378Z  Settings for the experiments that can be opted into. 2025-08-26T19:31:44.8501032Z  """ 2025-08-26T19:31:44.8501435Z  2025-08-26T19:31:44.8501885Z  experiments: dict[str, Experiment] = {} 2025-08-26T19:31:44.8502448Z  2025-08-26T19:31:44.8503050Z  2025-08-26T19:31:44.8503566Z class ColorFormatter(logging.Formatter): 2025-08-26T19:31:44.8504306Z  """Color codes the log messages based on the log level""" 2025-08-26T19:31:44.8504957Z  2025-08-26T19:31:44.8505338Z  COLORS = { 2025-08-26T19:31:44.8505830Z  "WARNING": "\033[33m", # Yellow 2025-08-26T19:31:44.8506408Z  "ERROR": "\033[31m", # Red 2025-08-26T19:31:44.8506959Z  "CRITICAL": "\033[31m", # Red 2025-08-26T19:31:44.8507532Z  "INFO": "\033[0m", # Reset 2025-08-26T19:31:44.8508094Z  "DEBUG": "\033[0m", # Reset 2025-08-26T19:31:44.8508632Z  } 2025-08-26T19:31:44.8509024Z  2025-08-26T19:31:44.8509482Z  def format(self, record: LogRecord) -> str: 2025-08-26T19:31:44.8510321Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:31:44.8511177Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:31:44.8511825Z  return super().format(record) 2025-08-26T19:31:44.8512371Z  2025-08-26T19:31:44.8512750Z  2025-08-26T19:31:44.8513460Z handler = logging.StreamHandler() 2025-08-26T19:31:44.8514312Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:31:44.8515089Z  2025-08-26T19:31:44.8515594Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:31:44.8516253Z log.addHandler(handler) 2025-08-26T19:31:44.8516775Z log.setLevel(logging.INFO) 2025-08-26T19:31:44.8517279Z  2025-08-26T19:31:44.8517646Z  2025-08-26T19:31:44.8518149Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:31:44.8518764Z  """ 2025-08-26T19:31:44.8519350Z  Defines outputs of the github action that invokes this script 2025-08-26T19:31:44.8520035Z  """ 2025-08-26T19:31:44.8520619Z  if not GITHUB_OUTPUT: 2025-08-26T19:31:44.8521780Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-26T19:31:44.8523094Z  log.warning( 2025-08-26T19:31:44.8524066Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-26T19:31:44.8525042Z  ) 2025-08-26T19:31:44.8525550Z  print(f"::set-output name={key}::{value}") 2025-08-26T19:31:44.8526150Z  return 2025-08-26T19:31:44.8526585Z  2025-08-26T19:31:44.8527019Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:31:44.8527665Z  log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:31:44.8528297Z  f.write(f"{key}={value}\n") 2025-08-26T19:31:44.8528829Z  2025-08-26T19:31:44.8529219Z  2025-08-26T19:31:44.8529778Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:31:44.8530495Z  return frozenset( 2025-08-26T19:31:44.8531210Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:31:44.8531944Z  ) 2025-08-26T19:31:44.8532363Z  2025-08-26T19:31:44.8532752Z  2025-08-26T19:31:44.8533270Z def parse_args() -> Any: 2025-08-26T19:31:44.8533965Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:31:44.8534933Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:31:44.8535783Z  parser.add_argument( 2025-08-26T19:31:44.8536320Z  "--github-issue-repo", 2025-08-26T19:31:44.8536871Z  type=str, 2025-08-26T19:31:44.8537354Z  required=False, 2025-08-26T19:31:44.8538030Z  default="pytorch/test-infra", 2025-08-26T19:31:44.8538660Z  help="GitHub repo to get the issue", 2025-08-26T19:31:44.8539246Z  ) 2025-08-26T19:31:44.8539683Z  parser.add_argument( 2025-08-26T19:31:44.8540212Z  "--github-repo", 2025-08-26T19:31:44.8540721Z  type=str, 2025-08-26T19:31:44.8541207Z  required=True, 2025-08-26T19:31:44.8541787Z  help="GitHub repo where CI is running", 2025-08-26T19:31:44.8542372Z  ) 2025-08-26T19:31:44.8542804Z  parser.add_argument( 2025-08-26T19:31:44.8543630Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:31:44.8544365Z  ) 2025-08-26T19:31:44.8544795Z  parser.add_argument( 2025-08-26T19:31:44.8545520Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:31:44.8546274Z  ) 2025-08-26T19:31:44.8546702Z  parser.add_argument( 2025-08-26T19:31:44.8547446Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:31:44.8548196Z  ) 2025-08-26T19:31:44.8548622Z  parser.add_argument( 2025-08-26T19:31:44.8549401Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:31:44.8550179Z  ) 2025-08-26T19:31:44.8550628Z  parser.add_argument( 2025-08-26T19:31:44.8551155Z  "--github-ref-type", 2025-08-26T19:31:44.8551683Z  type=str, 2025-08-26T19:31:44.8552162Z  required=True, 2025-08-26T19:31:44.8552766Z  help="Current GitHub ref type, branch or tag", 2025-08-26T19:31:44.8553681Z  ) 2025-08-26T19:31:44.8554142Z  parser.add_argument( 2025-08-26T19:31:44.8554713Z  "--eligible-experiments", 2025-08-26T19:31:44.8555515Z  type=_str_comma_separated_to_set, 2025-08-26T19:31:44.8556106Z  required=False, 2025-08-26T19:31:44.8556614Z  default="", 2025-08-26T19:31:44.8557588Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:31:44.8558598Z  ) 2025-08-26T19:31:44.8559027Z  parser.add_argument( 2025-08-26T19:31:44.8559569Z  "--opt-out-experiments", 2025-08-26T19:31:44.8560164Z  type=_str_comma_separated_to_set, 2025-08-26T19:31:44.8560744Z  required=False, 2025-08-26T19:31:44.8561249Z  default="", 2025-08-26T19:31:44.8561719Z  help=( 2025-08-26T19:31:44.8562492Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:31:44.8564341Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:31:44.8565253Z  ), 2025-08-26T19:31:44.8565681Z  ) 2025-08-26T19:31:44.8566110Z  parser.add_argument( 2025-08-26T19:31:44.8566636Z  "--pr-number", 2025-08-26T19:31:44.8567134Z  type=str, 2025-08-26T19:31:44.8567627Z  required=False, 2025-08-26T19:31:44.8568134Z  default="", 2025-08-26T19:31:44.8568723Z  help="the optional PR number where this is run", 2025-08-26T19:31:44.8569350Z  ) 2025-08-26T19:31:44.8569743Z  2025-08-26T19:31:44.8570166Z  return parser.parse_args() 2025-08-26T19:31:44.8570695Z  2025-08-26T19:31:44.8571067Z  2025-08-26T19:31:44.8571737Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.8572731Z  auth = Auth.Token(github_token) 2025-08-26T19:31:44.8573472Z  return Github(auth=auth) 2025-08-26T19:31:44.8573996Z  2025-08-26T19:31:44.8574372Z  2025-08-26T19:31:44.8575105Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.8576003Z  repo = gh.get_repo(repo) 2025-08-26T19:31:44.8576605Z  return repo.get_issue(number=issue_num) 2025-08-26T19:31:44.8577199Z  2025-08-26T19:31:44.8577572Z  2025-08-26T19:31:44.8577985Z def get_potential_pr_author( 2025-08-26T19:31:44.8578744Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:31:44.8579489Z ) -> str: 2025-08-26T19:31:44.8580105Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:31:44.8581012Z  # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:31:44.8581877Z  # embedded in the tag name: ciflow// 2025-08-26T19:31:44.8582526Z  2025-08-26T19:31:44.8583045Z  gh = get_gh_client(github_token) 2025-08-26T19:31:44.8583601Z  2025-08-26T19:31:44.8584130Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:31:44.8584849Z  split_tag = ref_name.split("/") 2025-08-26T19:31:44.8585415Z  if ( 2025-08-26T19:31:44.8585880Z  len(split_tag) == 3 2025-08-26T19:31:44.8586454Z  and split_tag[0] == "ciflow" 2025-08-26T19:31:44.8587074Z  and split_tag[2].isnumeric() 2025-08-26T19:31:44.8587660Z  ): 2025-08-26T19:31:44.8588127Z  pr_number = split_tag[2] 2025-08-26T19:31:44.8588693Z  try: 2025-08-26T19:31:44.8589213Z  repository = gh.get_repo(repo) 2025-08-26T19:31:44.8589926Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:31:44.8590755Z  except Exception as e: 2025-08-26T19:31:44.8591364Z  raise Exception( # noqa: TRY002 2025-08-26T19:31:44.8592125Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:31:44.8592852Z  ) from e 2025-08-26T19:31:44.8593830Z  return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:31:44.8594631Z  # In all other cases, return the original input username 2025-08-26T19:31:44.8595298Z  return username 2025-08-26T19:31:44.8595763Z  2025-08-26T19:31:44.8596142Z  2025-08-26T19:31:44.8596614Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:31:44.8597215Z  """ 2025-08-26T19:31:44.8597964Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:31:44.8598823Z  """ 2025-08-26T19:31:44.8599455Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:31:44.8600186Z  2025-08-26T19:31:44.8600560Z  2025-08-26T19:31:44.8601000Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:31:44.8601564Z  try: 2025-08-26T19:31:44.8602028Z  data = yaml.safe_load(yaml_text) 2025-08-26T19:31:44.8602607Z  return data 2025-08-26T19:31:44.8603225Z  except yaml.YAMLError: 2025-08-26T19:31:44.8603815Z  log.exception("Error loading YAML") 2025-08-26T19:31:44.8604395Z  raise 2025-08-26T19:31:44.8604827Z  2025-08-26T19:31:44.8605197Z  2025-08-26T19:31:44.8605898Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:31:44.8606719Z  """ 2025-08-26T19:31:44.8607587Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:31:44.8608436Z  2025-08-26T19:31:44.8609045Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.8609925Z  and the text below is the list of opted in users. 2025-08-26T19:31:44.8610548Z  2025-08-26T19:31:44.8611189Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:31:44.8611958Z  """ 2025-08-26T19:31:44.8612493Z  rollout_state_parts = rollout_state.split("---") 2025-08-26T19:31:44.8613275Z  if len(rollout_state_parts) >= 2: 2025-08-26T19:31:44.8613986Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:31:44.8614771Z  else: 2025-08-26T19:31:44.8615344Z  return "", rollout_state 2025-08-26T19:31:44.8615878Z  2025-08-26T19:31:44.8616237Z  2025-08-26T19:31:44.8616695Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:31:44.8617268Z  """ 2025-08-26T19:31:44.8617872Z  Dictionary of users with a list of features they have opted into 2025-08-26T19:31:44.8618582Z  """ 2025-08-26T19:31:44.8618981Z  2025-08-26T19:31:44.8619338Z  2025-08-26T19:31:44.8619931Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:31:44.8620664Z  """ 2025-08-26T19:31:44.8621476Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-26T19:31:44.8622391Z  2025-08-26T19:31:44.8623381Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-26T19:31:44.8624497Z  - Example line: "@User1,lf,split_build" 2025-08-26T19:31:44.8625273Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:31:44.8626107Z  2025-08-26T19:31:44.8626475Z  2025-08-26T19:31:44.8626836Z  """ 2025-08-26T19:31:44.8627266Z  optins = UserOptins() 2025-08-26T19:31:44.8627847Z  for user in user_optin_text.split("\n"): 2025-08-26T19:31:44.8628477Z  user = user.strip("\r\n\t -") 2025-08-26T19:31:44.8629109Z  if not user or not user.startswith("@"): 2025-08-26T19:31:44.8629731Z  # Not a valid user. Skip 2025-08-26T19:31:44.8630286Z  continue 2025-08-26T19:31:44.8630745Z  2025-08-26T19:31:44.8631160Z  if user: 2025-08-26T19:31:44.8631682Z  usr_name = user.split(",")[0].strip("@") 2025-08-26T19:31:44.8632463Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:31:44.8633440Z  2025-08-26T19:31:44.8633855Z  return optins 2025-08-26T19:31:44.8634323Z  2025-08-26T19:31:44.8634686Z  2025-08-26T19:31:44.8635244Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:31:44.8635926Z  """ 2025-08-26T19:31:44.8636390Z  Check if the experiment name is valid. 2025-08-26T19:31:44.8636973Z  A valid name: 2025-08-26T19:31:44.8637724Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:31:44.8638772Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:31:44.8639562Z  - Cannot contain spaces 2025-08-26T19:31:44.8640091Z  """ 2025-08-26T19:31:44.8640480Z  2025-08-26T19:31:44.8640984Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:31:44.8641790Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:31:44.8642656Z  2025-08-26T19:31:44.8643378Z  if valid: 2025-08-26T19:31:44.8643843Z  return True 2025-08-26T19:31:44.8644301Z  2025-08-26T19:31:44.8644684Z  log.error( 2025-08-26T19:31:44.8646307Z  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-08-26T19:31:44.8648005Z  ) 2025-08-26T19:31:44.8648415Z  return False 2025-08-26T19:31:44.8648876Z  2025-08-26T19:31:44.8649240Z  2025-08-26T19:31:44.8649813Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:31:44.8650528Z  """ 2025-08-26T19:31:44.8651202Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:31:44.8651981Z  """ 2025-08-26T19:31:44.8652391Z  try: 2025-08-26T19:31:44.8652803Z  if settings_text: 2025-08-26T19:31:44.8653737Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-26T19:31:44.8654611Z  # for easy reading 2025-08-26T19:31:44.8655551Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:31:44.8656547Z  # the backtick character in shell commands. 2025-08-26T19:31:44.8657224Z  backtick = chr(96) # backtick character 2025-08-26T19:31:44.8657982Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:31:44.8658724Z  settings = load_yaml(settings_text) 2025-08-26T19:31:44.8659285Z  2025-08-26T19:31:44.8659939Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:31:44.8660902Z  experiments = {} 2025-08-26T19:31:44.8661411Z  2025-08-26T19:31:44.8662028Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:31:44.8662884Z  if not is_valid_experiment_name(exp_name): 2025-08-26T19:31:44.8664196Z  # 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-08-26T19:31:44.8665346Z  continue 2025-08-26T19:31:44.8665847Z  2025-08-26T19:31:44.8666255Z  valid_settings = {} 2025-08-26T19:31:44.8666854Z  for setting in exp_settings: 2025-08-26T19:31:44.8667492Z  if setting not in Experiment._fields: 2025-08-26T19:31:44.8668121Z  log.warning( 2025-08-26T19:31:44.8668936Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:31:44.8669715Z  ) 2025-08-26T19:31:44.8670213Z  else: 2025-08-26T19:31:44.8670810Z  valid_settings[setting] = exp_settings[setting] 2025-08-26T19:31:44.8671438Z  2025-08-26T19:31:44.8671961Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:31:44.8672673Z  return Settings(experiments) 2025-08-26T19:31:44.8673324Z  2025-08-26T19:31:44.8673713Z  except Exception: 2025-08-26T19:31:44.8674291Z  log.exception("Failed to parse settings") 2025-08-26T19:31:44.8674874Z  2025-08-26T19:31:44.8675256Z  return Settings() 2025-08-26T19:31:44.8675719Z  2025-08-26T19:31:44.8676085Z  2025-08-26T19:31:44.8676717Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:31:44.8677364Z  """ 2025-08-26T19:31:44.8677870Z  Parse settings, if any, from the rollout state. 2025-08-26T19:31:44.8678469Z  2025-08-26T19:31:44.8679062Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.8679910Z  and the text below is the list of opted in users. 2025-08-26T19:31:44.8680520Z  2025-08-26T19:31:44.8681172Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:31:44.8681968Z  """ 2025-08-26T19:31:44.8682599Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.8683560Z  return parse_settings_from_text(settings_text) 2025-08-26T19:31:44.8684162Z  2025-08-26T19:31:44.8684529Z  2025-08-26T19:31:44.8685028Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:31:44.8685656Z  """ 2025-08-26T19:31:44.8686114Z  Parse users from the rollout state. 2025-08-26T19:31:44.8686672Z  2025-08-26T19:31:44.8687045Z  """ 2025-08-26T19:31:44.8687689Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.8688521Z  return parse_user_opt_in_from_text(users_text) 2025-08-26T19:31:44.8689130Z  2025-08-26T19:31:44.8689496Z  2025-08-26T19:31:44.8690182Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.8691017Z  """ 2025-08-26T19:31:44.8691504Z  Check if a user is opted into an experiment 2025-08-26T19:31:44.8692097Z  """ 2025-08-26T19:31:44.8692623Z  return experiment_name in user_optins.get(user, []) 2025-08-26T19:31:44.8693466Z  2025-08-26T19:31:44.8693828Z  2025-08-26T19:31:44.8694661Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.8695491Z  """ 2025-08-26T19:31:44.8696024Z  Check if a user explicitly opted out of an experiment 2025-08-26T19:31:44.8696667Z  """ 2025-08-26T19:31:44.8697243Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:31:44.8698027Z  experiment_optout = "-" + experiment_name 2025-08-26T19:31:44.8698752Z  if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:31:44.8699419Z  return False 2025-08-26T19:31:44.8699882Z  2025-08-26T19:31:44.8700397Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:31:44.8701072Z  log.warning( 2025-08-26T19:31:44.8702000Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:31:44.8703079Z  ) 2025-08-26T19:31:44.8703484Z  2025-08-26T19:31:44.8703865Z  return True 2025-08-26T19:31:44.8704300Z  2025-08-26T19:31:44.8704664Z  2025-08-26T19:31:44.8705054Z def get_runner_prefix( 2025-08-26T19:31:44.8705569Z  rollout_state: str, 2025-08-26T19:31:44.8706120Z  workflow_requestors: Iterable[str], 2025-08-26T19:31:44.8706688Z  branch: str, 2025-08-26T19:31:44.8707272Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.8708027Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.8708689Z  is_canary: bool = False, 2025-08-26T19:31:44.8709204Z ) -> str: 2025-08-26T19:31:44.8709706Z  settings = parse_settings(rollout_state) 2025-08-26T19:31:44.8710365Z  user_optins = parse_users(rollout_state) 2025-08-26T19:31:44.8710938Z  2025-08-26T19:31:44.8711463Z  fleet_prefix = "" 2025-08-26T19:31:44.8711959Z  prefixes = [] 2025-08-26T19:31:44.8712691Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:31:44.8713838Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:31:44.8714629Z  log.info( 2025-08-26T19:31:44.8715401Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:31:44.8716221Z  ) 2025-08-26T19:31:44.8716697Z  continue 2025-08-26T19:31:44.8717148Z  2025-08-26T19:31:44.8717555Z  if opt_out_experiments: 2025-08-26T19:31:44.8718176Z  if experiment_name in opt_out_experiments: 2025-08-26T19:31:44.8718909Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:31:44.8719559Z  log.info( 2025-08-26T19:31:44.8720611Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:31:44.8721706Z  ) 2025-08-26T19:31:44.8722174Z  continue 2025-08-26T19:31:44.8722659Z  2025-08-26T19:31:44.8723193Z  if eligible_experiments: 2025-08-26T19:31:44.8723851Z  if experiment_name not in eligible_experiments: 2025-08-26T19:31:44.8724562Z  exp_list = ", ".join(eligible_experiments) 2025-08-26T19:31:44.8725170Z  log.info( 2025-08-26T19:31:44.8726053Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:31:44.8726963Z  ) 2025-08-26T19:31:44.8727425Z  continue 2025-08-26T19:31:44.8728133Z  elif not experiment_settings.default: 2025-08-26T19:31:44.8728725Z  log.info( 2025-08-26T19:31:44.8729490Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:31:44.8730294Z  ) 2025-08-26T19:31:44.8730744Z  continue 2025-08-26T19:31:44.8731206Z  2025-08-26T19:31:44.8731727Z  # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:31:44.8732421Z  opted_out_users = [ 2025-08-26T19:31:44.8733065Z  requestor 2025-08-26T19:31:44.8733627Z  for requestor in workflow_requestors 2025-08-26T19:31:44.8734385Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.8735082Z  ] 2025-08-26T19:31:44.8735488Z  2025-08-26T19:31:44.8735894Z  if opted_out_users: 2025-08-26T19:31:44.8736448Z  log.info( 2025-08-26T19:31:44.8737174Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:31:44.8737947Z  ) 2025-08-26T19:31:44.8738384Z  continue 2025-08-26T19:31:44.8738848Z  2025-08-26T19:31:44.8739362Z  # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:31:44.8740044Z  opted_in_users = [ 2025-08-26T19:31:44.8740585Z  requestor 2025-08-26T19:31:44.8741136Z  for requestor in workflow_requestors 2025-08-26T19:31:44.8741893Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.8742575Z  ] 2025-08-26T19:31:44.8743084Z  2025-08-26T19:31:44.8743477Z  enabled = False 2025-08-26T19:31:44.8743994Z  if opted_in_users: 2025-08-26T19:31:44.8744643Z  log.info( 2025-08-26T19:31:44.8745365Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:31:44.8746116Z  ) 2025-08-26T19:31:44.8746566Z  enabled = True 2025-08-26T19:31:44.8747063Z  2025-08-26T19:31:44.8747508Z  elif experiment_settings.rollout_perc: 2025-08-26T19:31:44.8748431Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:31:44.8749461Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:31:44.8750181Z  log.info( 2025-08-26T19:31:44.8751165Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:31:44.8752157Z  ) 2025-08-26T19:31:44.8752644Z  enabled = True 2025-08-26T19:31:44.8753258Z  2025-08-26T19:31:44.8753638Z  if enabled: 2025-08-26T19:31:44.8754140Z  label = experiment_name 2025-08-26T19:31:44.8754770Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:31:44.8755678Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:31:44.8756638Z  # - If it's enabled, then we always list it's prefix first 2025-08-26T19:31:44.8757482Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:31:44.8758206Z  if is_canary: 2025-08-26T19:31:44.8758779Z  label += CANARY_FLEET_SUFFIX 2025-08-26T19:31:44.8759386Z  fleet_prefix = label 2025-08-26T19:31:44.8759927Z  else: 2025-08-26T19:31:44.8760442Z  prefixes.append(label) 2025-08-26T19:31:44.8761134Z  2025-08-26T19:31:44.8761526Z  if len(prefixes) > 1: 2025-08-26T19:31:44.8762031Z  log.error( 2025-08-26T19:31:44.8763285Z  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-08-26T19:31:44.8764493Z  ) 2025-08-26T19:31:44.8764939Z  prefixes = prefixes[:1] 2025-08-26T19:31:44.8765465Z  2025-08-26T19:31:44.8765864Z  # Fleet always comes first 2025-08-26T19:31:44.8766471Z  if fleet_prefix: 2025-08-26T19:31:44.8766997Z  prefixes.insert(0, fleet_prefix) 2025-08-26T19:31:44.8767550Z  2025-08-26T19:31:44.8768033Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:31:44.8768656Z  2025-08-26T19:31:44.8769022Z  2025-08-26T19:31:44.8769726Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:31:44.8770559Z  """ 2025-08-26T19:31:44.8771206Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:31:44.8771968Z  2025-08-26T19:31:44.8772593Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:31:44.8773458Z  """ 2025-08-26T19:31:44.8773911Z  gh = get_gh_client(github_token) 2025-08-26T19:31:44.8774526Z  issue = get_issue(gh, repo, issue_num) 2025-08-26T19:31:44.8775249Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:31:44.8775896Z  2025-08-26T19:31:44.8776261Z  2025-08-26T19:31:44.8776914Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:31:44.8777885Z  for _ in range(num_retries): 2025-08-26T19:31:44.8778441Z  try: 2025-08-26T19:31:44.8778933Z  req = Request(url=url, headers=headers) 2025-08-26T19:31:44.8779669Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:31:44.8780370Z  return json.loads(content) 2025-08-26T19:31:44.8780949Z  except Exception as e: 2025-08-26T19:31:44.8781586Z  log.warning(f"Could not download {url}: {e}") 2025-08-26T19:31:44.8782183Z  2025-08-26T19:31:44.8782812Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:31:44.8783687Z  return {} 2025-08-26T19:31:44.8784119Z  2025-08-26T19:31:44.8784479Z  2025-08-26T19:31:44.8784842Z @cache 2025-08-26T19:31:44.8785547Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:31:44.8786373Z  """ 2025-08-26T19:31:44.8786826Z  Dynamically get PR information 2025-08-26T19:31:44.8787393Z  """ 2025-08-26T19:31:44.8787961Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:31:44.8788638Z  headers = { 2025-08-26T19:31:44.8789172Z  "Accept": "application/vnd.github.v3+json", 2025-08-26T19:31:44.8789841Z  "Authorization": f"token {github_token}", 2025-08-26T19:31:44.8790418Z  } 2025-08-26T19:31:44.8790910Z  json_response: dict[str, Any] = download_json( 2025-08-26T19:31:44.8791588Z  url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:31:44.8792188Z  headers=headers, 2025-08-26T19:31:44.8792672Z  ) 2025-08-26T19:31:44.8793164Z  2025-08-26T19:31:44.8793562Z  if not json_response: 2025-08-26T19:31:44.8794231Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:31:44.8795058Z  return {} 2025-08-26T19:31:44.8795513Z  2025-08-26T19:31:44.8795898Z  return json_response 2025-08-26T19:31:44.8796387Z  2025-08-26T19:31:44.8796749Z  2025-08-26T19:31:44.8797392Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:31:44.8798177Z  """ 2025-08-26T19:31:44.8798770Z  Dynamically get the latest list of labels from the pull request 2025-08-26T19:31:44.8799484Z  """ 2025-08-26T19:31:44.8800025Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:31:44.8800700Z  return { 2025-08-26T19:31:44.8801360Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:31:44.8802097Z  } 2025-08-26T19:31:44.8802481Z  2025-08-26T19:31:44.8802846Z  2025-08-26T19:31:44.8803344Z def main() -> None: 2025-08-26T19:31:44.8803841Z  args = parse_args() 2025-08-26T19:31:44.8804328Z  2025-08-26T19:31:44.8804783Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:31:44.8805375Z  2025-08-26T19:31:44.8805780Z  # Check if the PR is opt-out 2025-08-26T19:31:44.8806339Z  if args.pr_number: 2025-08-26T19:31:44.8807091Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:31:44.8807912Z  if OPT_OUT_LABEL in labels: 2025-08-26T19:31:44.8808469Z  log.info( 2025-08-26T19:31:44.8809244Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:31:44.8810065Z  ) 2025-08-26T19:31:44.8810699Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.8811431Z  sys.exit() 2025-08-26T19:31:44.8812034Z  2025-08-26T19:31:44.8812407Z  try: 2025-08-26T19:31:44.8812908Z  rollout_state = get_rollout_state_from_issue( 2025-08-26T19:31:44.8813795Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:31:44.8814490Z  ) 2025-08-26T19:31:44.8814881Z  2025-08-26T19:31:44.8815314Z  username = get_potential_pr_author( 2025-08-26T19:31:44.8815912Z  args.github_token, 2025-08-26T19:31:44.8816457Z  args.github_repo, 2025-08-26T19:31:44.8817002Z  args.github_actor, 2025-08-26T19:31:44.8817560Z  args.github_ref_type, 2025-08-26T19:31:44.8818127Z  args.github_branch, 2025-08-26T19:31:44.8818642Z  ) 2025-08-26T19:31:44.8819046Z  2025-08-26T19:31:44.8819577Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:31:44.8820241Z  2025-08-26T19:31:44.8820699Z  runner_label_prefix = get_runner_prefix( 2025-08-26T19:31:44.8821295Z  rollout_state, 2025-08-26T19:31:44.8821866Z  (args.github_issue_owner, username), 2025-08-26T19:31:44.8822462Z  args.github_branch, 2025-08-26T19:31:44.8823130Z  args.eligible_experiments, 2025-08-26T19:31:44.8823728Z  args.opt_out_experiments, 2025-08-26T19:31:44.8824291Z  is_canary, 2025-08-26T19:31:44.8824768Z  ) 2025-08-26T19:31:44.8825159Z  2025-08-26T19:31:44.8825562Z  except Exception as e: 2025-08-26T19:31:44.8826081Z  log.error( 2025-08-26T19:31:44.8826858Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:31:44.8827662Z  ) 2025-08-26T19:31:44.8828071Z  2025-08-26T19:31:44.8828779Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.8829498Z  2025-08-26T19:31:44.8829864Z  2025-08-26T19:31:44.8830251Z if __name__ == "__main__": 2025-08-26T19:31:44.8830751Z  main() 2025-08-26T19:31:44.8831153Z  2025-08-26T19:31:44.8831523Z EOF 2025-08-26T19:31:44.8831897Z  2025-08-26T19:31:44.8832304Z cat runner_determinator.py 2025-08-26T19:31:44.9117020Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:44.9117994Z env: 2025-08-26T19:31:44.9118813Z GITHUB_TOKEN: *** 2025-08-26T19:31:44.9119321Z ISSUE_NUMBER: 5132 2025-08-26T19:31:44.9119866Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:44.9120479Z ISSUE_OWNER: 2025-08-26T19:31:44.9120965Z CHECK_EXPERIMENTS: 2025-08-26T19:31:44.9121478Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:31:44.9121956Z PR_NUMBER: 2025-08-26T19:31:44.9122357Z ##[endgroup] 2025-08-26T19:31:44.9341785Z # flake8: noqa: G004 2025-08-26T19:31:44.9342158Z 2025-08-26T19:31:44.9342622Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:31:44.9343994Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:31:44.9344861Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:31:44.9345337Z 2025-08-26T19:31:44.9345493Z """ 2025-08-26T19:31:44.9346097Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:31:44.9347023Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:31:44.9347972Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:31:44.9348832Z of which runners should be used to run which job. 2025-08-26T19:31:44.9349259Z 2025-08-26T19:31:44.9349653Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:31:44.9350814Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:31:44.9351769Z settings are considered to be empty with only the second part, the user 2025-08-26T19:31:44.9352518Z list, defined. 2025-08-26T19:31:44.9352755Z 2025-08-26T19:31:44.9353372Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:31:44.9354378Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:31:44.9355243Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:31:44.9355719Z 2025-08-26T19:31:44.9356103Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:31:44.9357018Z The user list is also a comma separated list of additional features or 2025-08-26T19:31:44.9357801Z experiments which the user could be opted in to. 2025-08-26T19:31:44.9358224Z 2025-08-26T19:31:44.9358438Z The user list has the following rules: 2025-08-26T19:31:44.9358797Z 2025-08-26T19:31:44.9359120Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:31:44.9360029Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:31:44.9360824Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:31:44.9361240Z 2025-08-26T19:31:44.9361413Z Example config: 2025-08-26T19:31:44.9361890Z # A list of experiments that can be opted into. 2025-08-26T19:31:44.9362582Z # This defines the behavior they'll induce when opted into. 2025-08-26T19:31:44.9363471Z # Expected syntax is: 2025-08-26T19:31:44.9364145Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:31:44.9365194Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:31:44.9365848Z 2025-08-26T19:31:44.9366025Z experiments: 2025-08-26T19:31:44.9366434Z lf: 2025-08-26T19:31:44.9366825Z rollout_percent: 25 2025-08-26T19:31:44.9367294Z all_branches: false 2025-08-26T19:31:44.9367998Z default: true 2025-08-26T19:31:44.9368441Z --- 2025-08-26T19:31:44.9368663Z 2025-08-26T19:31:44.9368827Z # Opt-ins: 2025-08-26T19:31:44.9369421Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:31:44.9370345Z # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:31:44.9371162Z # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:31:44.9371865Z # Experiments should be from the above list. 2025-08-26T19:31:44.9372271Z 2025-08-26T19:31:44.9372462Z @User1,-lf,split_build 2025-08-26T19:31:44.9372916Z @User2,lf 2025-08-26T19:31:44.9373587Z @User3,split_build 2025-08-26T19:31:44.9374015Z """ 2025-08-26T19:31:44.9374220Z 2025-08-26T19:31:44.9374386Z import json 2025-08-26T19:31:44.9374768Z import logging 2025-08-26T19:31:44.9375161Z import os 2025-08-26T19:31:44.9375532Z import random 2025-08-26T19:31:44.9375925Z import re 2025-08-26T19:31:44.9376289Z import sys 2025-08-26T19:31:44.9376729Z from argparse import ArgumentParser 2025-08-26T19:31:44.9377285Z from collections.abc import Iterable 2025-08-26T19:31:44.9377828Z from functools import cache 2025-08-26T19:31:44.9378327Z from logging import LogRecord 2025-08-26T19:31:44.9378845Z from typing import Any, NamedTuple 2025-08-26T19:31:44.9379411Z from urllib.request import Request, urlopen 2025-08-26T19:31:44.9379803Z 2025-08-26T19:31:44.9379969Z import yaml 2025-08-26T19:31:44.9380380Z from github import Auth, Github 2025-08-26T19:31:44.9380886Z from github.Issue import Issue 2025-08-26T19:31:44.9381217Z 2025-08-26T19:31:44.9381224Z 2025-08-26T19:31:44.9381448Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:31:44.9382166Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:31:44.9383290Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:31:44.9383896Z 2025-08-26T19:31:44.9384133Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:31:44.9384904Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:31:44.9385454Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:31:44.9386020Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:31:44.9386394Z 2025-08-26T19:31:44.9386594Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:31:44.9386939Z 2025-08-26T19:31:44.9387136Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:31:44.9387606Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:31:44.9387903Z 2025-08-26T19:31:44.9387915Z 2025-08-26T19:31:44.9388108Z class Experiment(NamedTuple): 2025-08-26T19:31:44.9388611Z rollout_perc: float = ( 2025-08-26T19:31:44.9389283Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:31:44.9389985Z ) 2025-08-26T19:31:44.9390367Z all_branches: bool = ( 2025-08-26T19:31:44.9391019Z False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:31:44.9391715Z ) 2025-08-26T19:31:44.9392086Z default: bool = ( 2025-08-26T19:31:44.9392682Z True # If True, the experiment is enabled by default for all queries 2025-08-26T19:31:44.9393566Z ) 2025-08-26T19:31:44.9393774Z 2025-08-26T19:31:44.9393958Z # Add more fields as needed 2025-08-26T19:31:44.9394276Z 2025-08-26T19:31:44.9394283Z 2025-08-26T19:31:44.9394472Z class Settings(NamedTuple): 2025-08-26T19:31:44.9394933Z """ 2025-08-26T19:31:44.9395390Z Settings for the experiments that can be opted into. 2025-08-26T19:31:44.9395990Z """ 2025-08-26T19:31:44.9396191Z 2025-08-26T19:31:44.9396405Z experiments: dict[str, Experiment] = {} 2025-08-26T19:31:44.9396787Z 2025-08-26T19:31:44.9396800Z 2025-08-26T19:31:44.9397013Z class ColorFormatter(logging.Formatter): 2025-08-26T19:31:44.9397663Z """Color codes the log messages based on the log level""" 2025-08-26T19:31:44.9398120Z 2025-08-26T19:31:44.9398284Z COLORS = { 2025-08-26T19:31:44.9398693Z "WARNING": "\033[33m", # Yellow 2025-08-26T19:31:44.9399215Z "ERROR": "\033[31m", # Red 2025-08-26T19:31:44.9399907Z "CRITICAL": "\033[31m", # Red 2025-08-26T19:31:44.9400438Z "INFO": "\033[0m", # Reset 2025-08-26T19:31:44.9400948Z "DEBUG": "\033[0m", # Reset 2025-08-26T19:31:44.9401431Z } 2025-08-26T19:31:44.9401643Z 2025-08-26T19:31:44.9401876Z def format(self, record: LogRecord) -> str: 2025-08-26T19:31:44.9402645Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:31:44.9403654Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:31:44.9404254Z return super().format(record) 2025-08-26T19:31:44.9404598Z 2025-08-26T19:31:44.9404604Z 2025-08-26T19:31:44.9404802Z handler = logging.StreamHandler() 2025-08-26T19:31:44.9405534Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:31:44.9406111Z 2025-08-26T19:31:44.9406356Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:31:44.9406958Z log.addHandler(handler) 2025-08-26T19:31:44.9407414Z log.setLevel(logging.INFO) 2025-08-26T19:31:44.9407725Z 2025-08-26T19:31:44.9407732Z 2025-08-26T19:31:44.9407988Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:31:44.9408568Z """ 2025-08-26T19:31:44.9409083Z Defines outputs of the github action that invokes this script 2025-08-26T19:31:44.9409735Z """ 2025-08-26T19:31:44.9410111Z if not GITHUB_OUTPUT: 2025-08-26T19:31:44.9411223Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-26T19:31:44.9412402Z log.warning( 2025-08-26T19:31:44.9413471Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-26T19:31:44.9414455Z ) 2025-08-26T19:31:44.9425607Z print(f"::set-output name={key}::{value}") 2025-08-26T19:31:44.9426250Z return 2025-08-26T19:31:44.9426487Z 2025-08-26T19:31:44.9426895Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:31:44.9427517Z log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:31:44.9428118Z f.write(f"{key}={value}\n") 2025-08-26T19:31:44.9428476Z 2025-08-26T19:31:44.9428483Z 2025-08-26T19:31:44.9428805Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:31:44.9429482Z return frozenset( 2025-08-26T19:31:44.9430114Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:31:44.9430841Z ) 2025-08-26T19:31:44.9431046Z 2025-08-26T19:31:44.9431053Z 2025-08-26T19:31:44.9431244Z def parse_args() -> Any: 2025-08-26T19:31:44.9431832Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:31:44.9432745Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:31:44.9433897Z parser.add_argument( 2025-08-26T19:31:44.9434394Z "--github-issue-repo", 2025-08-26T19:31:44.9434887Z type=str, 2025-08-26T19:31:44.9435322Z required=False, 2025-08-26T19:31:44.9435807Z default="pytorch/test-infra", 2025-08-26T19:31:44.9436385Z help="GitHub repo to get the issue", 2025-08-26T19:31:44.9436933Z ) 2025-08-26T19:31:44.9437328Z parser.add_argument( 2025-08-26T19:31:44.9437810Z "--github-repo", 2025-08-26T19:31:44.9438274Z type=str, 2025-08-26T19:31:44.9438697Z required=True, 2025-08-26T19:31:44.9439190Z help="GitHub repo where CI is running", 2025-08-26T19:31:44.9439756Z ) 2025-08-26T19:31:44.9440147Z parser.add_argument( 2025-08-26T19:31:44.9440806Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:31:44.9441537Z ) 2025-08-26T19:31:44.9441934Z parser.add_argument( 2025-08-26T19:31:44.9442599Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:31:44.9443564Z ) 2025-08-26T19:31:44.9501102Z parser.add_argument( 2025-08-26T19:31:44.9501991Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:31:44.9503306Z ) 2025-08-26T19:31:44.9503762Z parser.add_argument( 2025-08-26T19:31:44.9504596Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:31:44.9505519Z ) 2025-08-26T19:31:44.9505960Z parser.add_argument( 2025-08-26T19:31:44.9506510Z "--github-ref-type", 2025-08-26T19:31:44.9507059Z type=str, 2025-08-26T19:31:44.9507534Z required=True, 2025-08-26T19:31:44.9508113Z help="Current GitHub ref type, branch or tag", 2025-08-26T19:31:44.9508795Z ) 2025-08-26T19:31:44.9509227Z parser.add_argument( 2025-08-26T19:31:44.9509783Z "--eligible-experiments", 2025-08-26T19:31:44.9510401Z type=_str_comma_separated_to_set, 2025-08-26T19:31:44.9511035Z required=False, 2025-08-26T19:31:44.9511543Z default="", 2025-08-26T19:31:44.9512607Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:31:44.9513874Z ) 2025-08-26T19:31:44.9514282Z parser.add_argument( 2025-08-26T19:31:44.9514793Z "--opt-out-experiments", 2025-08-26T19:31:44.9515359Z type=_str_comma_separated_to_set, 2025-08-26T19:31:44.9515946Z required=False, 2025-08-26T19:31:44.9516408Z default="", 2025-08-26T19:31:44.9516842Z help=( 2025-08-26T19:31:44.9517599Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:31:44.9518949Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:31:44.9519944Z ), 2025-08-26T19:31:44.9520332Z ) 2025-08-26T19:31:44.9520739Z parser.add_argument( 2025-08-26T19:31:44.9521219Z "--pr-number", 2025-08-26T19:31:44.9521678Z type=str, 2025-08-26T19:31:44.9522114Z required=False, 2025-08-26T19:31:44.9522565Z default="", 2025-08-26T19:31:44.9523321Z help="the optional PR number where this is run", 2025-08-26T19:31:44.9523951Z ) 2025-08-26T19:31:44.9524157Z 2025-08-26T19:31:44.9524362Z return parser.parse_args() 2025-08-26T19:31:44.9524697Z 2025-08-26T19:31:44.9524704Z 2025-08-26T19:31:44.9525148Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.9525986Z auth = Auth.Token(github_token) 2025-08-26T19:31:44.9526523Z return Github(auth=auth) 2025-08-26T19:31:44.9526847Z 2025-08-26T19:31:44.9526854Z 2025-08-26T19:31:44.9527429Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.9528633Z repo = gh.get_repo(repo) 2025-08-26T19:31:44.9529183Z return repo.get_issue(number=issue_num) 2025-08-26T19:31:44.9529586Z 2025-08-26T19:31:44.9529592Z 2025-08-26T19:31:44.9529798Z def get_potential_pr_author( 2025-08-26T19:31:44.9530498Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:31:44.9531251Z ) -> str: 2025-08-26T19:31:44.9531797Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:31:44.9532680Z # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:31:44.9533696Z # embedded in the tag name: ciflow// 2025-08-26T19:31:44.9534139Z 2025-08-26T19:31:44.9534331Z gh = get_gh_client(github_token) 2025-08-26T19:31:44.9534678Z 2025-08-26T19:31:44.9534956Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:31:44.9535595Z split_tag = ref_name.split("/") 2025-08-26T19:31:44.9536113Z if ( 2025-08-26T19:31:44.9536502Z len(split_tag) == 3 2025-08-26T19:31:44.9536994Z and split_tag[0] == "ciflow" 2025-08-26T19:31:44.9537526Z and split_tag[2].isnumeric() 2025-08-26T19:31:44.9538034Z ): 2025-08-26T19:31:44.9538419Z pr_number = split_tag[2] 2025-08-26T19:31:44.9538917Z try: 2025-08-26T19:31:44.9539589Z repository = gh.get_repo(repo) 2025-08-26T19:31:44.9540208Z pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:31:44.9540833Z except Exception as e: 2025-08-26T19:31:44.9541352Z raise Exception( # noqa: TRY002 2025-08-26T19:31:44.9542044Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:31:44.9542710Z ) from e 2025-08-26T19:31:44.9543510Z return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:31:44.9544238Z # In all other cases, return the original input username 2025-08-26T19:31:44.9544930Z return username 2025-08-26T19:31:44.9545178Z 2025-08-26T19:31:44.9545185Z 2025-08-26T19:31:44.9545416Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:31:44.9545954Z """ 2025-08-26T19:31:44.9546610Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:31:44.9547424Z """ 2025-08-26T19:31:44.9547983Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:31:44.9548527Z 2025-08-26T19:31:44.9548534Z 2025-08-26T19:31:44.9548736Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:31:44.9549232Z try: 2025-08-26T19:31:44.9549625Z data = yaml.safe_load(yaml_text) 2025-08-26T19:31:44.9550139Z return data 2025-08-26T19:31:44.9550563Z except yaml.YAMLError: 2025-08-26T19:31:44.9551049Z log.exception("Error loading YAML") 2025-08-26T19:31:44.9551579Z raise 2025-08-26T19:31:44.9551798Z 2025-08-26T19:31:44.9551805Z 2025-08-26T19:31:44.9552243Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:31:44.9553110Z """ 2025-08-26T19:31:44.9553761Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:31:44.9554392Z 2025-08-26T19:31:44.9554894Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.9555694Z and the text below is the list of opted in users. 2025-08-26T19:31:44.9556125Z 2025-08-26T19:31:44.9556508Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:31:44.9557234Z """ 2025-08-26T19:31:44.9557678Z rollout_state_parts = rollout_state.split("---") 2025-08-26T19:31:44.9558323Z if len(rollout_state_parts) >= 2: 2025-08-26T19:31:44.9558947Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:31:44.9559551Z else: 2025-08-26T19:31:44.9559941Z return "", rollout_state 2025-08-26T19:31:44.9560258Z 2025-08-26T19:31:44.9560265Z 2025-08-26T19:31:44.9560472Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:31:44.9560993Z """ 2025-08-26T19:31:44.9561521Z Dictionary of users with a list of features they have opted into 2025-08-26T19:31:44.9562201Z """ 2025-08-26T19:31:44.9562408Z 2025-08-26T19:31:44.9562415Z 2025-08-26T19:31:44.9562776Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:31:44.9563587Z """ 2025-08-26T19:31:44.9564312Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-26T19:31:44.9565024Z 2025-08-26T19:31:44.9565661Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-26T19:31:44.9566683Z - Example line: "@User1,lf,split_build" 2025-08-26T19:31:44.9567379Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:31:44.9567869Z 2025-08-26T19:31:44.9567876Z 2025-08-26T19:31:44.9568030Z """ 2025-08-26T19:31:44.9568413Z optins = UserOptins() 2025-08-26T19:31:44.9568903Z for user in user_optin_text.split("\n"): 2025-08-26T19:31:44.9569474Z user = user.strip("\r\n\t -") 2025-08-26T19:31:44.9570027Z if not user or not user.startswith("@"): 2025-08-26T19:31:44.9570603Z # Not a valid user. Skip 2025-08-26T19:31:44.9571241Z continue 2025-08-26T19:31:44.9571497Z 2025-08-26T19:31:44.9571657Z if user: 2025-08-26T19:31:44.9572096Z usr_name = user.split(",")[0].strip("@") 2025-08-26T19:31:44.9572804Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:31:44.9573534Z 2025-08-26T19:31:44.9573713Z return optins 2025-08-26T19:31:44.9573958Z 2025-08-26T19:31:44.9573965Z 2025-08-26T19:31:44.9574248Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:31:44.9574863Z """ 2025-08-26T19:31:44.9575260Z Check if the experiment name is valid. 2025-08-26T19:31:44.9575790Z A valid name: 2025-08-26T19:31:44.9576432Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:31:44.9577422Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:31:44.9578163Z - Cannot contain spaces 2025-08-26T19:31:44.9578641Z """ 2025-08-26T19:31:44.9578845Z 2025-08-26T19:31:44.9579106Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:31:44.9579813Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:31:44.9580273Z 2025-08-26T19:31:44.9580432Z if valid: 2025-08-26T19:31:44.9580812Z return True 2025-08-26T19:31:44.9581049Z 2025-08-26T19:31:44.9581218Z log.error( 2025-08-26T19:31:44.9582784Z 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-08-26T19:31:44.9584720Z ) 2025-08-26T19:31:44.9585088Z return False 2025-08-26T19:31:44.9585329Z 2025-08-26T19:31:44.9585335Z 2025-08-26T19:31:44.9585651Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:31:44.9586305Z """ 2025-08-26T19:31:44.9587065Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:31:44.9587844Z """ 2025-08-26T19:31:44.9588203Z try: 2025-08-26T19:31:44.9588572Z if settings_text: 2025-08-26T19:31:44.9589329Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-26T19:31:44.9590165Z # for easy reading 2025-08-26T19:31:44.9590986Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:31:44.9591925Z # the backtick character in shell commands. 2025-08-26T19:31:44.9592547Z backtick = chr(96) # backtick character 2025-08-26T19:31:44.9593357Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:31:44.9594053Z settings = load_yaml(settings_text) 2025-08-26T19:31:44.9594453Z 2025-08-26T19:31:44.9594886Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:31:44.9595682Z experiments = {} 2025-08-26T19:31:44.9595996Z 2025-08-26T19:31:44.9596401Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:31:44.9597216Z if not is_valid_experiment_name(exp_name): 2025-08-26T19:31:44.9598397Z # 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-08-26T19:31:44.9599528Z continue 2025-08-26T19:31:44.9599818Z 2025-08-26T19:31:44.9600003Z valid_settings = {} 2025-08-26T19:31:44.9600543Z for setting in exp_settings: 2025-08-26T19:31:44.9601128Z if setting not in Experiment._fields: 2025-08-26T19:31:44.9601717Z log.warning( 2025-08-26T19:31:44.9602455Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:31:44.9603303Z ) 2025-08-26T19:31:44.9603887Z else: 2025-08-26T19:31:44.9604408Z valid_settings[setting] = exp_settings[setting] 2025-08-26T19:31:44.9604864Z 2025-08-26T19:31:44.9605148Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:31:44.9605796Z return Settings(experiments) 2025-08-26T19:31:44.9606165Z 2025-08-26T19:31:44.9606340Z except Exception: 2025-08-26T19:31:44.9606823Z log.exception("Failed to parse settings") 2025-08-26T19:31:44.9607227Z 2025-08-26T19:31:44.9607398Z return Settings() 2025-08-26T19:31:44.9607657Z 2025-08-26T19:31:44.9607669Z 2025-08-26T19:31:44.9607920Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:31:44.9608500Z """ 2025-08-26T19:31:44.9608955Z Parse settings, if any, from the rollout state. 2025-08-26T19:31:44.9609379Z 2025-08-26T19:31:44.9609740Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.9610546Z and the text below is the list of opted in users. 2025-08-26T19:31:44.9610971Z 2025-08-26T19:31:44.9611397Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:31:44.9612159Z """ 2025-08-26T19:31:44.9612729Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.9613772Z return parse_settings_from_text(settings_text) 2025-08-26T19:31:44.9614201Z 2025-08-26T19:31:44.9614208Z 2025-08-26T19:31:44.9614455Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:31:44.9615036Z """ 2025-08-26T19:31:44.9615424Z Parse users from the rollout state. 2025-08-26T19:31:44.9615786Z 2025-08-26T19:31:44.9615955Z """ 2025-08-26T19:31:44.9616550Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.9617748Z return parse_user_opt_in_from_text(users_text) 2025-08-26T19:31:44.9618193Z 2025-08-26T19:31:44.9618199Z 2025-08-26T19:31:44.9618803Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.9619601Z """ 2025-08-26T19:31:44.9620025Z Check if a user is opted into an experiment 2025-08-26T19:31:44.9620576Z """ 2025-08-26T19:31:44.9621161Z return experiment_name in user_optins.get(user, []) 2025-08-26T19:31:44.9621666Z 2025-08-26T19:31:44.9621673Z 2025-08-26T19:31:44.9622118Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.9622908Z """ 2025-08-26T19:31:44.9623675Z Check if a user explicitly opted out of an experiment 2025-08-26T19:31:44.9624287Z """ 2025-08-26T19:31:44.9624801Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:31:44.9625517Z experiment_optout = "-" + experiment_name 2025-08-26T19:31:44.9626184Z if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:31:44.9626812Z return False 2025-08-26T19:31:44.9627071Z 2025-08-26T19:31:44.9627368Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:31:44.9627989Z log.warning( 2025-08-26T19:31:44.9628829Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:31:44.9629760Z ) 2025-08-26T19:31:44.9629976Z 2025-08-26T19:31:44.9630141Z return True 2025-08-26T19:31:44.9630374Z 2025-08-26T19:31:44.9630381Z 2025-08-26T19:31:44.9630569Z def get_runner_prefix( 2025-08-26T19:31:44.9631005Z rollout_state: str, 2025-08-26T19:31:44.9631478Z workflow_requestors: Iterable[str], 2025-08-26T19:31:44.9632012Z branch: str, 2025-08-26T19:31:44.9632520Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.9633311Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.9633925Z is_canary: bool = False, 2025-08-26T19:31:44.9634391Z ) -> str: 2025-08-26T19:31:44.9634813Z settings = parse_settings(rollout_state) 2025-08-26T19:31:44.9635577Z user_optins = parse_users(rollout_state) 2025-08-26T19:31:44.9635952Z 2025-08-26T19:31:44.9636127Z fleet_prefix = "" 2025-08-26T19:31:44.9636555Z prefixes = [] 2025-08-26T19:31:44.9637178Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:31:44.9638151Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:31:44.9638876Z log.info( 2025-08-26T19:31:44.9639565Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:31:44.9640348Z ) 2025-08-26T19:31:44.9640726Z continue 2025-08-26T19:31:44.9640974Z 2025-08-26T19:31:44.9641165Z if opt_out_experiments: 2025-08-26T19:31:44.9641714Z if experiment_name in opt_out_experiments: 2025-08-26T19:31:44.9642373Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:31:44.9643066Z log.info( 2025-08-26T19:31:44.9644038Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:31:44.9645084Z ) 2025-08-26T19:31:44.9645479Z continue 2025-08-26T19:31:44.9645752Z 2025-08-26T19:31:44.9645946Z if eligible_experiments: 2025-08-26T19:31:44.9646541Z if experiment_name not in eligible_experiments: 2025-08-26T19:31:44.9647189Z exp_list = ", ".join(eligible_experiments) 2025-08-26T19:31:44.9647750Z log.info( 2025-08-26T19:31:44.9648550Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:31:44.9649420Z ) 2025-08-26T19:31:44.9649814Z continue 2025-08-26T19:31:44.9650295Z elif not experiment_settings.default: 2025-08-26T19:31:44.9650834Z log.info( 2025-08-26T19:31:44.9651636Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:31:44.9652411Z ) 2025-08-26T19:31:44.9652794Z continue 2025-08-26T19:31:44.9653254Z 2025-08-26T19:31:44.9653556Z # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:31:44.9654196Z opted_out_users = [ 2025-08-26T19:31:44.9654657Z requestor 2025-08-26T19:31:44.9655114Z for requestor in workflow_requestors 2025-08-26T19:31:44.9655800Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.9656434Z ] 2025-08-26T19:31:44.9656646Z 2025-08-26T19:31:44.9656828Z if opted_out_users: 2025-08-26T19:31:44.9657278Z log.info( 2025-08-26T19:31:44.9657902Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:31:44.9658613Z ) 2025-08-26T19:31:44.9658996Z continue 2025-08-26T19:31:44.9659246Z 2025-08-26T19:31:44.9659542Z # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:31:44.9660193Z opted_in_users = [ 2025-08-26T19:31:44.9660668Z requestor 2025-08-26T19:31:44.9661123Z for requestor in workflow_requestors 2025-08-26T19:31:44.9661802Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.9662428Z ] 2025-08-26T19:31:44.9662638Z 2025-08-26T19:31:44.9662811Z enabled = False 2025-08-26T19:31:44.9663494Z if opted_in_users: 2025-08-26T19:31:44.9663949Z log.info( 2025-08-26T19:31:44.9664560Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:31:44.9665252Z ) 2025-08-26T19:31:44.9665645Z enabled = True 2025-08-26T19:31:44.9665929Z 2025-08-26T19:31:44.9666146Z elif experiment_settings.rollout_perc: 2025-08-26T19:31:44.9667000Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:31:44.9668113Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:31:44.9668785Z log.info( 2025-08-26T19:31:44.9669657Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:31:44.9670586Z ) 2025-08-26T19:31:44.9670991Z enabled = True 2025-08-26T19:31:44.9671292Z 2025-08-26T19:31:44.9671455Z if enabled: 2025-08-26T19:31:44.9671875Z label = experiment_name 2025-08-26T19:31:44.9672421Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:31:44.9673380Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:31:44.9674291Z # - If it's enabled, then we always list it's prefix first 2025-08-26T19:31:44.9675074Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:31:44.9675759Z if is_canary: 2025-08-26T19:31:44.9676248Z label += CANARY_FLEET_SUFFIX 2025-08-26T19:31:44.9676804Z fleet_prefix = label 2025-08-26T19:31:44.9677289Z else: 2025-08-26T19:31:44.9677721Z prefixes.append(label) 2025-08-26T19:31:44.9678072Z 2025-08-26T19:31:44.9678254Z if len(prefixes) > 1: 2025-08-26T19:31:44.9678702Z log.error( 2025-08-26T19:31:44.9679751Z 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-08-26T19:31:44.9680875Z ) 2025-08-26T19:31:44.9681261Z prefixes = prefixes[:1] 2025-08-26T19:31:44.9681571Z 2025-08-26T19:31:44.9681758Z # Fleet always comes first 2025-08-26T19:31:44.9682233Z if fleet_prefix: 2025-08-26T19:31:44.9682679Z prefixes.insert(0, fleet_prefix) 2025-08-26T19:31:44.9683138Z 2025-08-26T19:31:44.9683531Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:31:44.9683964Z 2025-08-26T19:31:44.9683971Z 2025-08-26T19:31:44.9684425Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:31:44.9685198Z """ 2025-08-26T19:31:44.9685788Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:31:44.9686352Z 2025-08-26T19:31:44.9686739Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:31:44.9687449Z """ 2025-08-26T19:31:44.9687830Z gh = get_gh_client(github_token) 2025-08-26T19:31:44.9688382Z issue = get_issue(gh, repo, issue_num) 2025-08-26T19:31:44.9689023Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:31:44.9689467Z 2025-08-26T19:31:44.9689474Z 2025-08-26T19:31:44.9689871Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:31:44.9690640Z for _ in range(num_retries): 2025-08-26T19:31:44.9691121Z try: 2025-08-26T19:31:44.9691558Z req = Request(url=url, headers=headers) 2025-08-26T19:31:44.9692221Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:31:44.9692878Z return json.loads(content) 2025-08-26T19:31:44.9693641Z except Exception as e: 2025-08-26T19:31:44.9694187Z log.warning(f"Could not download {url}: {e}") 2025-08-26T19:31:44.9694598Z 2025-08-26T19:31:44.9694982Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:31:44.9695691Z return {} 2025-08-26T19:31:44.9695919Z 2025-08-26T19:31:44.9695926Z 2025-08-26T19:31:44.9696082Z @cache 2025-08-26T19:31:44.9696704Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:31:44.9697464Z """ 2025-08-26T19:31:44.9697858Z Dynamically get PR information 2025-08-26T19:31:44.9698341Z """ 2025-08-26T19:31:44.9698844Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:31:44.9699611Z headers = { 2025-08-26T19:31:44.9700072Z "Accept": "application/vnd.github.v3+json", 2025-08-26T19:31:44.9700680Z "Authorization": f"token {github_token}", 2025-08-26T19:31:44.9701232Z } 2025-08-26T19:31:44.9701650Z json_response: dict[str, Any] = download_json( 2025-08-26T19:31:44.9702257Z url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:31:44.9702803Z headers=headers, 2025-08-26T19:31:44.9703485Z ) 2025-08-26T19:31:44.9703694Z 2025-08-26T19:31:44.9703883Z if not json_response: 2025-08-26T19:31:44.9704447Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:31:44.9705063Z return {} 2025-08-26T19:31:44.9705298Z 2025-08-26T19:31:44.9705473Z return json_response 2025-08-26T19:31:44.9705752Z 2025-08-26T19:31:44.9705758Z 2025-08-26T19:31:44.9706159Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:31:44.9706899Z """ 2025-08-26T19:31:44.9707422Z Dynamically get the latest list of labels from the pull request 2025-08-26T19:31:44.9708073Z """ 2025-08-26T19:31:44.9708557Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:31:44.9709169Z return { 2025-08-26T19:31:44.9709758Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:31:44.9710473Z } 2025-08-26T19:31:44.9710671Z 2025-08-26T19:31:44.9710677Z 2025-08-26T19:31:44.9710850Z def main() -> None: 2025-08-26T19:31:44.9711271Z args = parse_args() 2025-08-26T19:31:44.9711543Z 2025-08-26T19:31:44.9711770Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:31:44.9712159Z 2025-08-26T19:31:44.9712351Z # Check if the PR is opt-out 2025-08-26T19:31:44.9712846Z if args.pr_number: 2025-08-26T19:31:44.9714135Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:31:44.9715065Z if OPT_OUT_LABEL in labels: 2025-08-26T19:31:44.9715577Z log.info( 2025-08-26T19:31:44.9716277Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:31:44.9717046Z ) 2025-08-26T19:31:44.9717599Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.9718272Z sys.exit() 2025-08-26T19:31:44.9718537Z 2025-08-26T19:31:44.9718698Z try: 2025-08-26T19:31:44.9719137Z rollout_state = get_rollout_state_from_issue( 2025-08-26T19:31:44.9719847Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:31:44.9720490Z ) 2025-08-26T19:31:44.9720695Z 2025-08-26T19:31:44.9720897Z username = get_potential_pr_author( 2025-08-26T19:31:44.9721446Z args.github_token, 2025-08-26T19:31:44.9721935Z args.github_repo, 2025-08-26T19:31:44.9722411Z args.github_actor, 2025-08-26T19:31:44.9722899Z args.github_ref_type, 2025-08-26T19:31:44.9723634Z args.github_branch, 2025-08-26T19:31:44.9724122Z ) 2025-08-26T19:31:44.9724334Z 2025-08-26T19:31:44.9724615Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:31:44.9725075Z 2025-08-26T19:31:44.9725294Z runner_label_prefix = get_runner_prefix( 2025-08-26T19:31:44.9725850Z rollout_state, 2025-08-26T19:31:44.9726341Z (args.github_issue_owner, username), 2025-08-26T19:31:44.9726900Z args.github_branch, 2025-08-26T19:31:44.9727406Z args.eligible_experiments, 2025-08-26T19:31:44.9727956Z args.opt_out_experiments, 2025-08-26T19:31:44.9728458Z is_canary, 2025-08-26T19:31:44.9728868Z ) 2025-08-26T19:31:44.9729079Z 2025-08-26T19:31:44.9729261Z except Exception as e: 2025-08-26T19:31:44.9729724Z log.error( 2025-08-26T19:31:44.9730394Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:31:44.9731316Z ) 2025-08-26T19:31:44.9731522Z 2025-08-26T19:31:44.9731856Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.9732357Z 2025-08-26T19:31:44.9732363Z 2025-08-26T19:31:44.9732538Z if __name__ == "__main__": 2025-08-26T19:31:44.9733096Z main() 2025-08-26T19:31:44.9733312Z 2025-08-26T19:31:44.9828792Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:31:44.9829719Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:31:44.9870688Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:44.9871172Z env: 2025-08-26T19:31:44.9871813Z GITHUB_TOKEN: *** 2025-08-26T19:31:44.9872232Z ISSUE_NUMBER: 5132 2025-08-26T19:31:44.9872681Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:44.9873328Z ISSUE_OWNER: 2025-08-26T19:31:44.9873742Z CHECK_EXPERIMENTS: 2025-08-26T19:31:44.9874189Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:31:44.9874625Z PR_NUMBER: 2025-08-26T19:31:44.9875018Z ##[endgroup] 2025-08-26T19:31:46.0122824Z Defaulting to user installation because normal site-packages is not writeable 2025-08-26T19:31:46.5419424Z Collecting urllib3==1.26.18 2025-08-26T19:31:46.5773896Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-26T19:31:46.5971377Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.9 MB/s eta 0:00:00 2025-08-26T19:31:46.6210418Z Collecting PyGithub==2.3.0 2025-08-26T19:31:46.6246984Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-26T19:31:46.6655674Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-26T19:31:46.6705508Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl.metadata (8.6 kB) 2025-08-26T19:31:46.6748690Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-26T19:31:46.6765918Z 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-08-26T19:31:46.6780784Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-26T19:31:46.7015477Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-26T19:31:46.7047389Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-26T19:31:46.7269504Z 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-08-26T19:31:46.8380911Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:31:46.8415522Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-26T19:31:46.9621460Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-26T19:31:46.9719583Z 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-08-26T19:31:46.9991903Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:31:47.0027588Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-26T19:31:47.0338027Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-26T19:31:47.0514862Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 13.0 MB/s eta 0:00:00 2025-08-26T19:31:47.1338059Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-26T19:31:47.1459344Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 35.2 MB/s eta 0:00:00 2025-08-26T19:31:47.1502507Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-08-26T19:31:47.1670509Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 56.7 MB/s eta 0:00:00 2025-08-26T19:31:47.1702491Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-26T19:31:47.1762849Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-26T19:31:47.1898329Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 40.3 MB/s eta 0:00:00 2025-08-26T19:31:47.1964295Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-08-26T19:31:47.2005191Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 31.6 MB/s eta 0:00:00 2025-08-26T19:31:47.2034390Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-26T19:31:47.2083957Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 31.7 MB/s eta 0:00:00 2025-08-26T19:31:47.4970893Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-26T19:31:48.0278854Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-08-26T19:31:48.1097789Z ##[group]Run curr_branch="main" 2025-08-26T19:31:48.1098083Z curr_branch="main" 2025-08-26T19:31:48.1098303Z curr_ref_type="branch" 2025-08-26T19:31:48.1098555Z echo "Current branch is '$curr_branch'" 2025-08-26T19:31:48.1098834Z  2025-08-26T19:31:48.1099026Z python3 runner_determinator.py \ 2025-08-26T19:31:48.1099303Z  --github-token "$GITHUB_TOKEN" \ 2025-08-26T19:31:48.1099583Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-26T19:31:48.1099844Z  --github-branch "$curr_branch" \ 2025-08-26T19:31:48.1100103Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-26T19:31:48.1100383Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-26T19:31:48.1100659Z  --github-ref-type "$curr_ref_type" \ 2025-08-26T19:31:48.1100931Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-26T19:31:48.1101224Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-26T19:31:48.1101584Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-26T19:31:48.1101874Z  --pr-number "${PR_NUMBER}" 2025-08-26T19:31:48.1144156Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:48.1144399Z env: 2025-08-26T19:31:48.1144964Z GITHUB_TOKEN: *** 2025-08-26T19:31:48.1145160Z ISSUE_NUMBER: 5132 2025-08-26T19:31:48.1145383Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:48.1145621Z ISSUE_OWNER: 2025-08-26T19:31:48.1145808Z CHECK_EXPERIMENTS: 2025-08-26T19:31:48.1146007Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:31:48.1146196Z PR_NUMBER: 2025-08-26T19:31:48.1146372Z ##[endgroup] 2025-08-26T19:31:48.1204689Z Current branch is 'main' 2025-08-26T19:31:50.2522167Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-26T19:31:50.2523924Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-26T19:31:50.2524639Z INFO : Setting output: label-type='' 2025-08-26T19:31:50.2846357Z Evaluate and set job outputs 2025-08-26T19:31:50.2853342Z Cleaning up orphan processes