2025-10-10T00:01:08.1072137Z Current runner version: '2.328.0' 2025-10-10T00:01:08.1093766Z ##[group]Runner Image Provisioner 2025-10-10T00:01:08.1094564Z Hosted Compute Agent 2025-10-10T00:01:08.1095006Z Version: 20250912.392 2025-10-10T00:01:08.1095659Z Commit: d921fda672a98b64f4f82364647e2f10b2267d0b 2025-10-10T00:01:08.1096315Z Build Date: 2025-09-12T15:23:14Z 2025-10-10T00:01:08.1096803Z ##[endgroup] 2025-10-10T00:01:08.1097392Z ##[group]Operating System 2025-10-10T00:01:08.1097876Z Ubuntu 2025-10-10T00:01:08.1098290Z 24.04.3 2025-10-10T00:01:08.1098713Z LTS 2025-10-10T00:01:08.1099135Z ##[endgroup] 2025-10-10T00:01:08.1099687Z ##[group]Runner Image 2025-10-10T00:01:08.1100205Z Image: ubuntu-24.04 2025-10-10T00:01:08.1100651Z Version: 20250929.60.1 2025-10-10T00:01:08.1101499Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250929.60/images/ubuntu/Ubuntu2404-Readme.md 2025-10-10T00:01:08.1102868Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250929.60 2025-10-10T00:01:08.1104078Z ##[endgroup] 2025-10-10T00:01:08.1104967Z ##[group]GITHUB_TOKEN Permissions 2025-10-10T00:01:08.1107003Z Contents: read 2025-10-10T00:01:08.1107504Z Metadata: read 2025-10-10T00:01:08.1107931Z ##[endgroup] 2025-10-10T00:01:08.1110037Z Secret source: Actions 2025-10-10T00:01:08.1110716Z Prepare workflow directory 2025-10-10T00:01:08.1618997Z Prepare all required actions 2025-10-10T00:01:08.1672567Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (344e6365a0068c2d2847fcec0c55dd53291d475e) 2025-10-10T00:01:08.1677305Z ##[group] Inputs 2025-10-10T00:01:08.1677836Z check_experiments: 2025-10-10T00:01:08.1678385Z opt_out_experiments: 2025-10-10T00:01:08.1678895Z triggering_actor: pytorchmergebot 2025-10-10T00:01:08.1679627Z issue_owner: 2025-10-10T00:01:08.1680128Z curr_branch: main 2025-10-10T00:01:08.1680623Z curr_ref_type: branch 2025-10-10T00:01:08.1681165Z issue_number: 5132 2025-10-10T00:01:08.1681656Z ##[endgroup] 2025-10-10T00:01:08.1682253Z Complete job name: before-test / get-label-type / runner-determinator 2025-10-10T00:01:08.8275664Z ##[group]Run cat < runner_determinator.py 2025-10-10T00:01:08.8277964Z cat < runner_determinator.py 2025-10-10T00:01:08.8278662Z # flake8: noqa: G004 2025-10-10T00:01:08.8279374Z  2025-10-10T00:01:08.8280183Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:01:08.8281247Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:01:08.8282285Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:01:08.8283016Z  2025-10-10T00:01:08.8283436Z """ 2025-10-10T00:01:08.8284215Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:01:08.8285240Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:01:08.8286483Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:01:08.8287465Z of which runners should be used to run which job. 2025-10-10T00:01:08.8288181Z  2025-10-10T00:01:08.8288939Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:01:08.8290076Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:01:08.8291135Z settings are considered to be empty with only the second part, the user 2025-10-10T00:01:08.8291954Z list, defined. 2025-10-10T00:01:08.8292518Z  2025-10-10T00:01:08.8293199Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:01:08.8294299Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:01:08.8295339Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:01:08.8296056Z  2025-10-10T00:01:08.8297023Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:01:08.8298041Z The user list is also a comma separated list of additional features or 2025-10-10T00:01:08.8298963Z experiments which the user could be opted in to. 2025-10-10T00:01:08.8300109Z  2025-10-10T00:01:08.8300652Z The user list has the following rules: 2025-10-10T00:01:08.8301295Z  2025-10-10T00:01:08.8301935Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:01:08.8303023Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:01:08.8303980Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:01:08.8304625Z  2025-10-10T00:01:08.8305152Z Example config: 2025-10-10T00:01:08.8305770Z  # A list of experiments that can be opted into. 2025-10-10T00:01:08.8306603Z  # This defines the behavior they'll induce when opted into. 2025-10-10T00:01:08.8307404Z  # Expected syntax is: 2025-10-10T00:01:08.8308245Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:01:08.8309779Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:01:08.8310689Z  2025-10-10T00:01:08.8311169Z  experiments: 2025-10-10T00:01:08.8311684Z  lf: 2025-10-10T00:01:08.8312231Z  rollout_percent: 25 2025-10-10T00:01:08.8312876Z  all_branches: false 2025-10-10T00:01:08.8313455Z  default: true 2025-10-10T00:01:08.8314044Z  --- 2025-10-10T00:01:08.8314498Z  2025-10-10T00:01:08.8314973Z  # Opt-ins: 2025-10-10T00:01:08.8315723Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:01:08.8316978Z  # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:01:08.8317951Z  # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:01:08.8318770Z  # Experiments should be from the above list. 2025-10-10T00:01:08.8319613Z  2025-10-10T00:01:08.8320083Z  @User1,-lf,split_build 2025-10-10T00:01:08.8320700Z  @User2,lf 2025-10-10T00:01:08.8321261Z  @User3,split_build 2025-10-10T00:01:08.8321839Z """ 2025-10-10T00:01:08.8322324Z  2025-10-10T00:01:08.8322780Z import json 2025-10-10T00:01:08.8323323Z import logging 2025-10-10T00:01:08.8323818Z import os 2025-10-10T00:01:08.8324387Z import random 2025-10-10T00:01:08.8324888Z import re 2025-10-10T00:01:08.8325388Z import sys 2025-10-10T00:01:08.8325956Z from argparse import ArgumentParser 2025-10-10T00:01:08.8326748Z from collections.abc import Iterable 2025-10-10T00:01:08.8327431Z from functools import cache 2025-10-10T00:01:08.8328095Z from logging import LogRecord 2025-10-10T00:01:08.8328763Z from typing import Any, NamedTuple 2025-10-10T00:01:08.8329549Z from urllib.request import Request, urlopen 2025-10-10T00:01:08.8330234Z  2025-10-10T00:01:08.8330701Z import yaml 2025-10-10T00:01:08.8331228Z from github import Auth, Github 2025-10-10T00:01:08.8331921Z from github.Issue import Issue 2025-10-10T00:01:08.8332495Z  2025-10-10T00:01:08.8332947Z  2025-10-10T00:01:08.8333526Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:01:08.8334358Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:01:08.8335420Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:01:08.8336233Z  2025-10-10T00:01:08.8336979Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:01:08.8337677Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:01:08.8338372Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:01:08.8339127Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:01:08.8339824Z  2025-10-10T00:01:08.8340377Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:01:08.8340991Z  2025-10-10T00:01:08.8341471Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:01:08.8342047Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:01:08.8342659Z  2025-10-10T00:01:08.8343109Z  2025-10-10T00:01:08.8343586Z class Experiment(NamedTuple): 2025-10-10T00:01:08.8344274Z  rollout_perc: float = ( 2025-10-10T00:01:08.8345087Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:01:08.8345886Z  ) 2025-10-10T00:01:08.8346395Z  all_branches: bool = ( 2025-10-10T00:01:08.8347242Z  False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:01:08.8348062Z  ) 2025-10-10T00:01:08.8348566Z  default: bool = ( 2025-10-10T00:01:08.8349464Z  True # If True, the experiment is enabled by default for all queries 2025-10-10T00:01:08.8350216Z  ) 2025-10-10T00:01:08.8350712Z  2025-10-10T00:01:08.8351169Z  # Add more fields as needed 2025-10-10T00:01:08.8351795Z  2025-10-10T00:01:08.8352265Z  2025-10-10T00:01:08.8352730Z class Settings(NamedTuple): 2025-10-10T00:01:08.8353329Z  """ 2025-10-10T00:01:08.8353937Z  Settings for the experiments that can be opted into. 2025-10-10T00:01:08.8354660Z  """ 2025-10-10T00:01:08.8355125Z  2025-10-10T00:01:08.8355663Z  experiments: dict[str, Experiment] = {} 2025-10-10T00:01:08.8356308Z  2025-10-10T00:01:08.8356855Z  2025-10-10T00:01:08.8357426Z class ColorFormatter(logging.Formatter): 2025-10-10T00:01:08.8358222Z  """Color codes the log messages based on the log level""" 2025-10-10T00:01:08.8358964Z  2025-10-10T00:01:08.8359460Z  COLORS = { 2025-10-10T00:01:08.8360099Z  "WARNING": "\033[33m", # Yellow 2025-10-10T00:01:08.8360732Z  "ERROR": "\033[31m", # Red 2025-10-10T00:01:08.8361379Z  "CRITICAL": "\033[31m", # Red 2025-10-10T00:01:08.8362068Z  "INFO": "\033[0m", # Reset 2025-10-10T00:01:08.8362716Z  "DEBUG": "\033[0m", # Reset 2025-10-10T00:01:08.8363311Z  } 2025-10-10T00:01:08.8363796Z  2025-10-10T00:01:08.8364342Z  def format(self, record: LogRecord) -> str: 2025-10-10T00:01:08.8365232Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:01:08.8366210Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:01:08.8366963Z  return super().format(record) 2025-10-10T00:01:08.8367560Z  2025-10-10T00:01:08.8368071Z  2025-10-10T00:01:08.8368547Z handler = logging.StreamHandler() 2025-10-10T00:01:08.8369679Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:01:08.8370531Z  2025-10-10T00:01:08.8371163Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:01:08.8371908Z log.addHandler(handler) 2025-10-10T00:01:08.8372506Z log.setLevel(logging.INFO) 2025-10-10T00:01:08.8373096Z  2025-10-10T00:01:08.8373521Z  2025-10-10T00:01:08.8374132Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:01:08.8374841Z  """ 2025-10-10T00:01:08.8375548Z  Defines outputs of the github action that invokes this script 2025-10-10T00:01:08.8376469Z  """ 2025-10-10T00:01:08.8376962Z  if not GITHUB_OUTPUT: 2025-10-10T00:01:08.8378207Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:01:08.8379489Z  log.warning( 2025-10-10T00:01:08.8380585Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:01:08.8381630Z  ) 2025-10-10T00:01:08.8382179Z  print(f"::set-output name={key}::{value}") 2025-10-10T00:01:08.8382898Z  return 2025-10-10T00:01:08.8383389Z  2025-10-10T00:01:08.8383896Z  with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:01:08.8384670Z  log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:01:08.8385393Z  f.write(f"{key}={value}\n") 2025-10-10T00:01:08.8386050Z  2025-10-10T00:01:08.8386508Z  2025-10-10T00:01:08.8387148Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:01:08.8387911Z  return frozenset( 2025-10-10T00:01:08.8388745Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:01:08.8389677Z  ) 2025-10-10T00:01:08.8390132Z  2025-10-10T00:01:08.8390605Z  2025-10-10T00:01:08.8391053Z def parse_args() -> Any: 2025-10-10T00:01:08.8391826Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:01:08.8392825Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:01:08.8393701Z  parser.add_argument( 2025-10-10T00:01:08.8394309Z  "--github-issue-repo", 2025-10-10T00:01:08.8394918Z  type=str, 2025-10-10T00:01:08.8395502Z  required=False, 2025-10-10T00:01:08.8396204Z  default="pytorch/test-infra", 2025-10-10T00:01:08.8396910Z  help="GitHub repo to get the issue", 2025-10-10T00:01:08.8397521Z  ) 2025-10-10T00:01:08.8398016Z  parser.add_argument( 2025-10-10T00:01:08.8398624Z  "--github-repo", 2025-10-10T00:01:08.8399379Z  type=str, 2025-10-10T00:01:08.8399970Z  required=True, 2025-10-10T00:01:08.8400575Z  help="GitHub repo where CI is running", 2025-10-10T00:01:08.8401261Z  ) 2025-10-10T00:01:08.8401742Z  parser.add_argument( 2025-10-10T00:01:08.8402512Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:01:08.8403331Z  ) 2025-10-10T00:01:08.8403833Z  parser.add_argument( 2025-10-10T00:01:08.8404638Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:01:08.8405435Z  ) 2025-10-10T00:01:08.8405938Z  parser.add_argument( 2025-10-10T00:01:08.8406731Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:01:08.8407552Z  ) 2025-10-10T00:01:08.8408081Z  parser.add_argument( 2025-10-10T00:01:08.8408897Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:01:08.8409824Z  ) 2025-10-10T00:01:08.8410300Z  parser.add_argument( 2025-10-10T00:01:08.8410903Z  "--github-ref-type", 2025-10-10T00:01:08.8411485Z  type=str, 2025-10-10T00:01:08.8412079Z  required=True, 2025-10-10T00:01:08.8412771Z  help="Current GitHub ref type, branch or tag", 2025-10-10T00:01:08.8413474Z  ) 2025-10-10T00:01:08.8413940Z  parser.add_argument( 2025-10-10T00:01:08.8414656Z  "--eligible-experiments", 2025-10-10T00:01:08.8415328Z  type=_str_comma_separated_to_set, 2025-10-10T00:01:08.8416030Z  required=False, 2025-10-10T00:01:08.8416616Z  default="", 2025-10-10T00:01:08.8417634Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:01:08.8418810Z  ) 2025-10-10T00:01:08.8419452Z  parser.add_argument( 2025-10-10T00:01:08.8420199Z  "--opt-out-experiments", 2025-10-10T00:01:08.8420880Z  type=_str_comma_separated_to_set, 2025-10-10T00:01:08.8421542Z  required=False, 2025-10-10T00:01:08.8422130Z  default="", 2025-10-10T00:01:08.8422673Z  help=( 2025-10-10T00:01:08.8423522Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:01:08.8424759Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:01:08.8425778Z  ), 2025-10-10T00:01:08.8426274Z  ) 2025-10-10T00:01:08.8426730Z  parser.add_argument( 2025-10-10T00:01:08.8427371Z  "--pr-number", 2025-10-10T00:01:08.8427922Z  type=str, 2025-10-10T00:01:08.8428466Z  required=False, 2025-10-10T00:01:08.8429053Z  default="", 2025-10-10T00:01:08.8429829Z  help="the optional PR number where this is run", 2025-10-10T00:01:08.8430509Z  ) 2025-10-10T00:01:08.8430990Z  2025-10-10T00:01:08.8431488Z  return parser.parse_args() 2025-10-10T00:01:08.8432069Z  2025-10-10T00:01:08.8432551Z  2025-10-10T00:01:08.8433250Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.8434239Z  auth = Auth.Token(github_token) 2025-10-10T00:01:08.8435042Z  return Github(auth=auth) 2025-10-10T00:01:08.8435597Z  2025-10-10T00:01:08.8436070Z  2025-10-10T00:01:08.8436841Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.8437790Z  repo = gh.get_repo(repo) 2025-10-10T00:01:08.8438448Z  return repo.get_issue(number=issue_num) 2025-10-10T00:01:08.8439107Z  2025-10-10T00:01:08.8439614Z  2025-10-10T00:01:08.8440123Z def get_potential_pr_author( 2025-10-10T00:01:08.8440968Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:01:08.8441758Z ) -> str: 2025-10-10T00:01:08.8442441Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:01:08.8443364Z  # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:01:08.8444330Z  # embedded in the tag name: ciflow// 2025-10-10T00:01:08.8445056Z  2025-10-10T00:01:08.8445517Z  gh = get_gh_client(github_token) 2025-10-10T00:01:08.8446171Z  2025-10-10T00:01:08.8446747Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:01:08.8447504Z  split_tag = ref_name.split("/") 2025-10-10T00:01:08.8448127Z  if ( 2025-10-10T00:01:08.8448695Z  len(split_tag) == 3 2025-10-10T00:01:08.8449392Z  and split_tag[0] == "ciflow" 2025-10-10T00:01:08.8450089Z  and split_tag[2].isnumeric() 2025-10-10T00:01:08.8450722Z  ): 2025-10-10T00:01:08.8451243Z  pr_number = split_tag[2] 2025-10-10T00:01:08.8451890Z  try: 2025-10-10T00:01:08.8452481Z  repository = gh.get_repo(repo) 2025-10-10T00:01:08.8453345Z  pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:01:08.8454123Z  except Exception as e: 2025-10-10T00:01:08.8454784Z  raise Exception( # noqa: TRY002 2025-10-10T00:01:08.8455639Z  f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:01:08.8456412Z  ) from e 2025-10-10T00:01:08.8457173Z  return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:01:08.8458037Z  # In all other cases, return the original input username 2025-10-10T00:01:08.8458747Z  return username 2025-10-10T00:01:08.8459369Z  2025-10-10T00:01:08.8459787Z  2025-10-10T00:01:08.8460347Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:01:08.8461012Z  """ 2025-10-10T00:01:08.8461818Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:01:08.8462728Z  """ 2025-10-10T00:01:08.8463417Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:01:08.8464220Z  2025-10-10T00:01:08.8464619Z  2025-10-10T00:01:08.8465194Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:01:08.8465791Z  try: 2025-10-10T00:01:08.8466319Z  data = yaml.safe_load(yaml_text) 2025-10-10T00:01:08.8467004Z  return data 2025-10-10T00:01:08.8467556Z  except yaml.YAMLError: 2025-10-10T00:01:08.8468204Z  log.exception("Error loading YAML") 2025-10-10T00:01:08.8468871Z  raise 2025-10-10T00:01:08.8469493Z  2025-10-10T00:01:08.8469910Z  2025-10-10T00:01:08.8470676Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:01:08.8471538Z  """ 2025-10-10T00:01:08.8472395Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:01:08.8473312Z  2025-10-10T00:01:08.8473957Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.8474881Z  and the text below is the list of opted in users. 2025-10-10T00:01:08.8475616Z  2025-10-10T00:01:08.8476287Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:01:08.8477141Z  """ 2025-10-10T00:01:08.8477720Z  rollout_state_parts = rollout_state.split("---") 2025-10-10T00:01:08.8478475Z  if len(rollout_state_parts) >= 2: 2025-10-10T00:01:08.8479501Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:01:08.8480239Z  else: 2025-10-10T00:01:08.8480778Z  return "", rollout_state 2025-10-10T00:01:08.8481351Z  2025-10-10T00:01:08.8481825Z  2025-10-10T00:01:08.8482329Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:01:08.8482966Z  """ 2025-10-10T00:01:08.8483597Z  Dictionary of users with a list of features they have opted into 2025-10-10T00:01:08.8484599Z  """ 2025-10-10T00:01:08.8485087Z  2025-10-10T00:01:08.8485480Z  2025-10-10T00:01:08.8486176Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:01:08.8486914Z  """ 2025-10-10T00:01:08.8487770Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:01:08.8488719Z  2025-10-10T00:01:08.8489726Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:01:08.8490875Z  - Example line: "@User1,lf,split_build" 2025-10-10T00:01:08.8491829Z  - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:01:08.8492564Z  2025-10-10T00:01:08.8492965Z  2025-10-10T00:01:08.8493423Z  """ 2025-10-10T00:01:08.8493911Z  optins = UserOptins() 2025-10-10T00:01:08.8494572Z  for user in user_optin_text.split("\n"): 2025-10-10T00:01:08.8495290Z  user = user.strip("\r\n\t -") 2025-10-10T00:01:08.8495962Z  if not user or not user.startswith("@"): 2025-10-10T00:01:08.8496656Z  # Not a valid user. Skip 2025-10-10T00:01:08.8497242Z  continue 2025-10-10T00:01:08.8497792Z  2025-10-10T00:01:08.8498210Z  if user: 2025-10-10T00:01:08.8498833Z  usr_name = user.split(",")[0].strip("@") 2025-10-10T00:01:08.8499752Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:01:08.8500491Z  2025-10-10T00:01:08.8500964Z  return optins 2025-10-10T00:01:08.8501482Z  2025-10-10T00:01:08.8501913Z  2025-10-10T00:01:08.8502496Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:01:08.8503272Z  """ 2025-10-10T00:01:08.8503818Z  Check if the experiment name is valid. 2025-10-10T00:01:08.8504427Z  A valid name: 2025-10-10T00:01:08.8505298Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:01:08.8506330Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:01:08.8507135Z  - Cannot contain spaces 2025-10-10T00:01:08.8507762Z  """ 2025-10-10T00:01:08.8508195Z  2025-10-10T00:01:08.8508774Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:01:08.8509792Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:01:08.8510612Z  2025-10-10T00:01:08.8511043Z  if valid: 2025-10-10T00:01:08.8511600Z  return True 2025-10-10T00:01:08.8512141Z  2025-10-10T00:01:08.8512563Z  log.error( 2025-10-10T00:01:08.8514186Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-10-10T00:01:08.8515759Z  ) 2025-10-10T00:01:08.8516231Z  return False 2025-10-10T00:01:08.8516763Z  2025-10-10T00:01:08.8517165Z  2025-10-10T00:01:08.8517803Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:01:08.8518553Z  """ 2025-10-10T00:01:08.8519365Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:01:08.8520149Z  """ 2025-10-10T00:01:08.8520635Z  try: 2025-10-10T00:01:08.8521117Z  if settings_text: 2025-10-10T00:01:08.8522017Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:01:08.8522955Z  # for easy reading 2025-10-10T00:01:08.8523878Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:01:08.8524887Z  # the backtick character in shell commands. 2025-10-10T00:01:08.8525651Z  backtick = chr(96) # backtick character 2025-10-10T00:01:08.8526461Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:01:08.8527263Z  settings = load_yaml(settings_text) 2025-10-10T00:01:08.8527867Z  2025-10-10T00:01:08.8528619Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:01:08.8529634Z  experiments = {} 2025-10-10T00:01:08.8530174Z  2025-10-10T00:01:08.8530907Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:01:08.8531757Z  if not is_valid_experiment_name(exp_name): 2025-10-10T00:01:08.8532998Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-10-10T00:01:08.8534194Z  continue 2025-10-10T00:01:08.8534732Z  2025-10-10T00:01:08.8535208Z  valid_settings = {} 2025-10-10T00:01:08.8535884Z  for setting in exp_settings: 2025-10-10T00:01:08.8536608Z  if setting not in Experiment._fields: 2025-10-10T00:01:08.8537275Z  log.warning( 2025-10-10T00:01:08.8538156Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:01:08.8538980Z  ) 2025-10-10T00:01:08.8539580Z  else: 2025-10-10T00:01:08.8540287Z  valid_settings[setting] = exp_settings[setting] 2025-10-10T00:01:08.8540937Z  2025-10-10T00:01:08.8541552Z  experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:01:08.8542346Z  return Settings(experiments) 2025-10-10T00:01:08.8542917Z  2025-10-10T00:01:08.8543375Z  except Exception: 2025-10-10T00:01:08.8544000Z  log.exception("Failed to parse settings") 2025-10-10T00:01:08.8544684Z  2025-10-10T00:01:08.8545106Z  return Settings() 2025-10-10T00:01:08.8545649Z  2025-10-10T00:01:08.8546075Z  2025-10-10T00:01:08.8546733Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:01:08.8547459Z  """ 2025-10-10T00:01:08.8548045Z  Parse settings, if any, from the rollout state. 2025-10-10T00:01:08.8548713Z  2025-10-10T00:01:08.8549456Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.8550431Z  and the text below is the list of opted in users. 2025-10-10T00:01:08.8551107Z  2025-10-10T00:01:08.8551793Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:01:08.8552688Z  """ 2025-10-10T00:01:08.8553366Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.8554230Z  return parse_settings_from_text(settings_text) 2025-10-10T00:01:08.8554894Z  2025-10-10T00:01:08.8555336Z  2025-10-10T00:01:08.8555906Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:01:08.8556608Z  """ 2025-10-10T00:01:08.8557139Z  Parse users from the rollout state. 2025-10-10T00:01:08.8557732Z  2025-10-10T00:01:08.8558180Z  """ 2025-10-10T00:01:08.8558814Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.8560080Z  return parse_user_opt_in_from_text(users_text) 2025-10-10T00:01:08.8560789Z  2025-10-10T00:01:08.8561192Z  2025-10-10T00:01:08.8561951Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.8562781Z  """ 2025-10-10T00:01:08.8563348Z  Check if a user is opted into an experiment 2025-10-10T00:01:08.8563976Z  """ 2025-10-10T00:01:08.8564589Z  return experiment_name in user_optins.get(user, []) 2025-10-10T00:01:08.8565288Z  2025-10-10T00:01:08.8565786Z  2025-10-10T00:01:08.8566556Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.8567413Z  """ 2025-10-10T00:01:08.8568029Z  Check if a user explicitly opted out of an experiment 2025-10-10T00:01:08.8568709Z  """ 2025-10-10T00:01:08.8569475Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:01:08.8570306Z  experiment_optout = "-" + experiment_name 2025-10-10T00:01:08.8571049Z  if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:01:08.8571810Z  return False 2025-10-10T00:01:08.8572312Z  2025-10-10T00:01:08.8572900Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:01:08.8667167Z  log.warning( 2025-10-10T00:01:08.8668830Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:01:08.8670774Z  ) 2025-10-10T00:01:08.8671468Z  2025-10-10T00:01:08.8672066Z  return True 2025-10-10T00:01:08.8672509Z  2025-10-10T00:01:08.8672865Z  2025-10-10T00:01:08.8673251Z def get_runner_prefix( 2025-10-10T00:01:08.8673754Z  rollout_state: str, 2025-10-10T00:01:08.8674298Z  workflow_requestors: Iterable[str], 2025-10-10T00:01:08.8674855Z  branch: str, 2025-10-10T00:01:08.8675444Z  eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.8676234Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.8676870Z  is_canary: bool = False, 2025-10-10T00:01:08.8677384Z ) -> str: 2025-10-10T00:01:08.8677895Z  settings = parse_settings(rollout_state) 2025-10-10T00:01:08.8678542Z  user_optins = parse_users(rollout_state) 2025-10-10T00:01:08.8679118Z  2025-10-10T00:01:08.8679900Z  fleet_prefix = "" 2025-10-10T00:01:08.8680381Z  prefixes = [] 2025-10-10T00:01:08.8681087Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:01:08.8682094Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:01:08.8682843Z  log.info( 2025-10-10T00:01:08.8683609Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:01:08.8684396Z  ) 2025-10-10T00:01:08.8684851Z  continue 2025-10-10T00:01:08.8685298Z  2025-10-10T00:01:08.8685718Z  if opt_out_experiments: 2025-10-10T00:01:08.8686336Z  if experiment_name in opt_out_experiments: 2025-10-10T00:01:08.8687047Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:01:08.8687699Z  log.info( 2025-10-10T00:01:08.8688699Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:01:08.8689834Z  ) 2025-10-10T00:01:08.8690299Z  continue 2025-10-10T00:01:08.8690770Z  2025-10-10T00:01:08.8691182Z  if eligible_experiments: 2025-10-10T00:01:08.8691820Z  if experiment_name not in eligible_experiments: 2025-10-10T00:01:08.8692529Z  exp_list = ", ".join(eligible_experiments) 2025-10-10T00:01:08.8693128Z  log.info( 2025-10-10T00:01:08.8693989Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:01:08.8694862Z  ) 2025-10-10T00:01:08.8695410Z  continue 2025-10-10T00:01:08.8695984Z  elif not experiment_settings.default: 2025-10-10T00:01:08.8696563Z  log.info( 2025-10-10T00:01:08.8697326Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:01:08.8698094Z  ) 2025-10-10T00:01:08.8698548Z  continue 2025-10-10T00:01:08.8699016Z  2025-10-10T00:01:08.8699642Z  # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:01:08.8700310Z  opted_out_users = [ 2025-10-10T00:01:08.8700829Z  requestor 2025-10-10T00:01:08.8701373Z  for requestor in workflow_requestors 2025-10-10T00:01:08.8702114Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.8702784Z  ] 2025-10-10T00:01:08.8703181Z  2025-10-10T00:01:08.8703589Z  if opted_out_users: 2025-10-10T00:01:08.8704134Z  log.info( 2025-10-10T00:01:08.8704856Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:01:08.8705597Z  ) 2025-10-10T00:01:08.8706036Z  continue 2025-10-10T00:01:08.8706489Z  2025-10-10T00:01:08.8706996Z  # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:01:08.8707660Z  opted_in_users = [ 2025-10-10T00:01:08.8708190Z  requestor 2025-10-10T00:01:08.8708729Z  for requestor in workflow_requestors 2025-10-10T00:01:08.8709636Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.8710298Z  ] 2025-10-10T00:01:08.8710699Z  2025-10-10T00:01:08.8711077Z  enabled = False 2025-10-10T00:01:08.8711596Z  if opted_in_users: 2025-10-10T00:01:08.8712199Z  log.info( 2025-10-10T00:01:08.8712882Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:01:08.8713605Z  ) 2025-10-10T00:01:08.8714050Z  enabled = True 2025-10-10T00:01:08.8714541Z  2025-10-10T00:01:08.8714985Z  elif experiment_settings.rollout_perc: 2025-10-10T00:01:08.8715884Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:01:08.8716903Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:01:08.8717604Z  log.info( 2025-10-10T00:01:08.8718552Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:01:08.8719883Z  ) 2025-10-10T00:01:08.8720369Z  enabled = True 2025-10-10T00:01:08.8720869Z  2025-10-10T00:01:08.8721248Z  if enabled: 2025-10-10T00:01:08.8721763Z  label = experiment_name 2025-10-10T00:01:08.8722393Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:01:08.8723297Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:01:08.8724235Z  # - If it's enabled, then we always list it's prefix first 2025-10-10T00:01:08.8725063Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:01:08.8725781Z  if is_canary: 2025-10-10T00:01:08.8726353Z  label += CANARY_FLEET_SUFFIX 2025-10-10T00:01:08.8726957Z  fleet_prefix = label 2025-10-10T00:01:08.8727494Z  else: 2025-10-10T00:01:08.8728085Z  prefixes.append(label) 2025-10-10T00:01:08.8728625Z  2025-10-10T00:01:08.8729015Z  if len(prefixes) > 1: 2025-10-10T00:01:08.8729636Z  log.error( 2025-10-10T00:01:08.8730959Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-10-10T00:01:08.8732109Z  ) 2025-10-10T00:01:08.8732560Z  prefixes = prefixes[:1] 2025-10-10T00:01:08.8733078Z  2025-10-10T00:01:08.8733470Z  # Fleet always comes first 2025-10-10T00:01:08.8734006Z  if fleet_prefix: 2025-10-10T00:01:08.8734531Z  prefixes.insert(0, fleet_prefix) 2025-10-10T00:01:08.8735067Z  2025-10-10T00:01:08.8735562Z  return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:01:08.8736163Z  2025-10-10T00:01:08.8736534Z  2025-10-10T00:01:08.8737219Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:01:08.8738030Z  """ 2025-10-10T00:01:08.8738678Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:01:08.8739522Z  2025-10-10T00:01:08.8740143Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:01:08.8740883Z  """ 2025-10-10T00:01:08.8741342Z  gh = get_gh_client(github_token) 2025-10-10T00:01:08.8741953Z  issue = get_issue(gh, repo, issue_num) 2025-10-10T00:01:08.8742677Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:01:08.8743309Z  2025-10-10T00:01:08.8743678Z  2025-10-10T00:01:08.8744329Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:01:08.8745240Z  for _ in range(num_retries): 2025-10-10T00:01:08.8745774Z  try: 2025-10-10T00:01:08.8746280Z  req = Request(url=url, headers=headers) 2025-10-10T00:01:08.8747012Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:01:08.8747709Z  return json.loads(content) 2025-10-10T00:01:08.8748290Z  except Exception as e: 2025-10-10T00:01:08.8748930Z  log.warning(f"Could not download {url}: {e}") 2025-10-10T00:01:08.8749716Z  2025-10-10T00:01:08.8750338Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:01:08.8751082Z  return {} 2025-10-10T00:01:08.8751514Z  2025-10-10T00:01:08.8751871Z  2025-10-10T00:01:08.8752239Z @cache 2025-10-10T00:01:08.8752936Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:01:08.8753744Z  """ 2025-10-10T00:01:08.8754201Z  Dynamically get PR information 2025-10-10T00:01:08.8754733Z  """ 2025-10-10T00:01:08.8755314Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:01:08.8755980Z  headers = { 2025-10-10T00:01:08.8756527Z  "Accept": "application/vnd.github.v3+json", 2025-10-10T00:01:08.8757197Z  "Authorization": f"token {github_token}", 2025-10-10T00:01:08.8757770Z  } 2025-10-10T00:01:08.8758266Z  json_response: dict[str, Any] = download_json( 2025-10-10T00:01:08.8758933Z  url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:01:08.8759671Z  headers=headers, 2025-10-10T00:01:08.8760149Z  ) 2025-10-10T00:01:08.8760538Z  2025-10-10T00:01:08.8760937Z  if not json_response: 2025-10-10T00:01:08.8761614Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:01:08.8762380Z  return {} 2025-10-10T00:01:08.8762832Z  2025-10-10T00:01:08.8763223Z  return json_response 2025-10-10T00:01:08.8763705Z  2025-10-10T00:01:08.8764064Z  2025-10-10T00:01:08.8764700Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:01:08.8765462Z  """ 2025-10-10T00:01:08.8766059Z  Dynamically get the latest list of labels from the pull request 2025-10-10T00:01:08.8766754Z  """ 2025-10-10T00:01:08.8767304Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:01:08.8767952Z  return { 2025-10-10T00:01:08.8768617Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:01:08.8769451Z  } 2025-10-10T00:01:08.8769824Z  2025-10-10T00:01:08.8770182Z  2025-10-10T00:01:08.8770559Z def main() -> None: 2025-10-10T00:01:08.8771031Z  args = parse_args() 2025-10-10T00:01:08.8771506Z  2025-10-10T00:01:08.8771968Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:01:08.8772534Z  2025-10-10T00:01:08.8772945Z  # Check if the PR is opt-out 2025-10-10T00:01:08.8773487Z  if args.pr_number: 2025-10-10T00:01:08.8774230Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:01:08.8775028Z  if OPT_OUT_LABEL in labels: 2025-10-10T00:01:08.8775598Z  log.info( 2025-10-10T00:01:08.8776375Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:01:08.8777172Z  ) 2025-10-10T00:01:08.8777824Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.8778542Z  sys.exit() 2025-10-10T00:01:08.8779100Z  2025-10-10T00:01:08.8779620Z  try: 2025-10-10T00:01:08.8780129Z  rollout_state = get_rollout_state_from_issue( 2025-10-10T00:01:08.8780921Z  args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:01:08.8781594Z  ) 2025-10-10T00:01:08.8781998Z  2025-10-10T00:01:08.8782431Z  username = get_potential_pr_author( 2025-10-10T00:01:08.8783021Z  args.github_token, 2025-10-10T00:01:08.8783559Z  args.github_repo, 2025-10-10T00:01:08.8784098Z  args.github_actor, 2025-10-10T00:01:08.8784653Z  args.github_ref_type, 2025-10-10T00:01:08.8785224Z  args.github_branch, 2025-10-10T00:01:08.8785742Z  ) 2025-10-10T00:01:08.8786146Z  2025-10-10T00:01:08.8786675Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:01:08.8787318Z  2025-10-10T00:01:08.8787776Z  runner_label_prefix = get_runner_prefix( 2025-10-10T00:01:08.8788372Z  rollout_state, 2025-10-10T00:01:08.8788941Z  (args.github_issue_owner, username), 2025-10-10T00:01:08.8789710Z  args.github_branch, 2025-10-10T00:01:08.8790284Z  args.eligible_experiments, 2025-10-10T00:01:08.8790904Z  args.opt_out_experiments, 2025-10-10T00:01:08.8791472Z  is_canary, 2025-10-10T00:01:08.8791956Z  ) 2025-10-10T00:01:08.8792365Z  2025-10-10T00:01:08.8792777Z  except Exception as e: 2025-10-10T00:01:08.8793295Z  log.error( 2025-10-10T00:01:08.8794071Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:01:08.8794967Z  ) 2025-10-10T00:01:08.8795373Z  2025-10-10T00:01:08.8795949Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.8796632Z  2025-10-10T00:01:08.8796995Z  2025-10-10T00:01:08.8797377Z if __name__ == "__main__": 2025-10-10T00:01:08.8797879Z  main() 2025-10-10T00:01:08.8798282Z  2025-10-10T00:01:08.8798644Z EOF 2025-10-10T00:01:08.8799024Z  2025-10-10T00:01:08.8800052Z cat runner_determinator.py 2025-10-10T00:01:09.0056806Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:09.0057694Z env: 2025-10-10T00:01:09.0058465Z GITHUB_TOKEN: *** 2025-10-10T00:01:09.0058881Z ISSUE_NUMBER: 5132 2025-10-10T00:01:09.0059518Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:09.0060017Z ISSUE_OWNER: 2025-10-10T00:01:09.0060421Z CHECK_EXPERIMENTS: 2025-10-10T00:01:09.0060853Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:01:09.0061289Z PR_NUMBER: 2025-10-10T00:01:09.0061708Z ##[endgroup] 2025-10-10T00:01:09.0244652Z # flake8: noqa: G004 2025-10-10T00:01:09.0245015Z 2025-10-10T00:01:09.0245450Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:01:09.0246420Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:01:09.0247241Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:01:09.0247691Z 2025-10-10T00:01:09.0247844Z """ 2025-10-10T00:01:09.0248416Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:01:09.0249523Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:01:09.0250465Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:01:09.0251282Z of which runners should be used to run which job. 2025-10-10T00:01:09.0251700Z 2025-10-10T00:01:09.0252073Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:01:09.0253175Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:01:09.0254060Z settings are considered to be empty with only the second part, the user 2025-10-10T00:01:09.0254787Z list, defined. 2025-10-10T00:01:09.0255022Z 2025-10-10T00:01:09.0255386Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:01:09.0256327Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:01:09.0257161Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:01:09.0257625Z 2025-10-10T00:01:09.0257988Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:01:09.0258864Z The user list is also a comma separated list of additional features or 2025-10-10T00:01:09.0260031Z experiments which the user could be opted in to. 2025-10-10T00:01:09.0260452Z 2025-10-10T00:01:09.0260651Z The user list has the following rules: 2025-10-10T00:01:09.0260990Z 2025-10-10T00:01:09.0261322Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:01:09.0262198Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:01:09.0262973Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:01:09.0263380Z 2025-10-10T00:01:09.0263547Z Example config: 2025-10-10T00:01:09.0264017Z # A list of experiments that can be opted into. 2025-10-10T00:01:09.0264705Z # This defines the behavior they'll induce when opted into. 2025-10-10T00:01:09.0265342Z # Expected syntax is: 2025-10-10T00:01:09.0266001Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:01:09.0267005Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:01:09.0267619Z 2025-10-10T00:01:09.0267790Z experiments: 2025-10-10T00:01:09.0268182Z lf: 2025-10-10T00:01:09.0268561Z rollout_percent: 25 2025-10-10T00:01:09.0269146Z all_branches: false 2025-10-10T00:01:09.0269851Z default: true 2025-10-10T00:01:09.0270257Z --- 2025-10-10T00:01:09.0270468Z 2025-10-10T00:01:09.0270630Z # Opt-ins: 2025-10-10T00:01:09.0271213Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:01:09.0272071Z # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:01:09.0272861Z # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:01:09.0273524Z # Experiments should be from the above list. 2025-10-10T00:01:09.0273911Z 2025-10-10T00:01:09.0274099Z @User1,-lf,split_build 2025-10-10T00:01:09.0274550Z @User2,lf 2025-10-10T00:01:09.0274935Z @User3,split_build 2025-10-10T00:01:09.0275346Z """ 2025-10-10T00:01:09.0275541Z 2025-10-10T00:01:09.0275705Z import json 2025-10-10T00:01:09.0276069Z import logging 2025-10-10T00:01:09.0276454Z import os 2025-10-10T00:01:09.0276814Z import random 2025-10-10T00:01:09.0277201Z import re 2025-10-10T00:01:09.0277571Z import sys 2025-10-10T00:01:09.0277971Z from argparse import ArgumentParser 2025-10-10T00:01:09.0278511Z from collections.abc import Iterable 2025-10-10T00:01:09.0279039Z from functools import cache 2025-10-10T00:01:09.0279777Z from logging import LogRecord 2025-10-10T00:01:09.0280282Z from typing import Any, NamedTuple 2025-10-10T00:01:09.0280838Z from urllib.request import Request, urlopen 2025-10-10T00:01:09.0281201Z 2025-10-10T00:01:09.0281362Z import yaml 2025-10-10T00:01:09.0281755Z from github import Auth, Github 2025-10-10T00:01:09.0282248Z from github.Issue import Issue 2025-10-10T00:01:09.0282570Z 2025-10-10T00:01:09.0282578Z 2025-10-10T00:01:09.0282792Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:01:09.0283486Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:01:09.0284455Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:01:09.0285035Z 2025-10-10T00:01:09.0285272Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:01:09.0285962Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:01:09.0286486Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:01:09.0287046Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:01:09.0287416Z 2025-10-10T00:01:09.0287606Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:01:09.0287942Z 2025-10-10T00:01:09.0288132Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:01:09.0288583Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:01:09.0288870Z 2025-10-10T00:01:09.0288877Z 2025-10-10T00:01:09.0289067Z class Experiment(NamedTuple): 2025-10-10T00:01:09.0289783Z rollout_perc: float = ( 2025-10-10T00:01:09.0290422Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:01:09.0291106Z ) 2025-10-10T00:01:09.0291472Z all_branches: bool = ( 2025-10-10T00:01:09.0292123Z False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:01:09.0292797Z ) 2025-10-10T00:01:09.0293165Z default: bool = ( 2025-10-10T00:01:09.0293731Z True # If True, the experiment is enabled by default for all queries 2025-10-10T00:01:09.0294364Z ) 2025-10-10T00:01:09.0294559Z 2025-10-10T00:01:09.0294737Z # Add more fields as needed 2025-10-10T00:01:09.0295042Z 2025-10-10T00:01:09.0295049Z 2025-10-10T00:01:09.0295232Z class Settings(NamedTuple): 2025-10-10T00:01:09.0295681Z """ 2025-10-10T00:01:09.0296129Z Settings for the experiments that can be opted into. 2025-10-10T00:01:09.0296707Z """ 2025-10-10T00:01:09.0296900Z 2025-10-10T00:01:09.0297110Z experiments: dict[str, Experiment] = {} 2025-10-10T00:01:09.0297488Z 2025-10-10T00:01:09.0297496Z 2025-10-10T00:01:09.0297707Z class ColorFormatter(logging.Formatter): 2025-10-10T00:01:09.0298348Z """Color codes the log messages based on the log level""" 2025-10-10T00:01:09.0298791Z 2025-10-10T00:01:09.0298953Z COLORS = { 2025-10-10T00:01:09.0299467Z "WARNING": "\033[33m", # Yellow 2025-10-10T00:01:09.0300088Z "ERROR": "\033[31m", # Red 2025-10-10T00:01:09.0300622Z "CRITICAL": "\033[31m", # Red 2025-10-10T00:01:09.0301141Z "INFO": "\033[0m", # Reset 2025-10-10T00:01:09.0301650Z "DEBUG": "\033[0m", # Reset 2025-10-10T00:01:09.0302117Z } 2025-10-10T00:01:09.0302317Z 2025-10-10T00:01:09.0302536Z def format(self, record: LogRecord) -> str: 2025-10-10T00:01:09.0303265Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:01:09.0304070Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:01:09.0304667Z return super().format(record) 2025-10-10T00:01:09.0305008Z 2025-10-10T00:01:09.0305015Z 2025-10-10T00:01:09.0305206Z handler = logging.StreamHandler() 2025-10-10T00:01:09.0305920Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:01:09.0306483Z 2025-10-10T00:01:09.0306734Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:01:09.0307317Z log.addHandler(handler) 2025-10-10T00:01:09.0307768Z log.setLevel(logging.INFO) 2025-10-10T00:01:09.0308070Z 2025-10-10T00:01:09.0308077Z 2025-10-10T00:01:09.0308335Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:01:09.0308900Z """ 2025-10-10T00:01:09.0309609Z Defines outputs of the github action that invokes this script 2025-10-10T00:01:09.0310244Z """ 2025-10-10T00:01:09.0310608Z if not GITHUB_OUTPUT: 2025-10-10T00:01:09.0311682Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:01:09.0312778Z log.warning( 2025-10-10T00:01:09.0313636Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:01:09.0314566Z ) 2025-10-10T00:01:09.0324187Z print(f"::set-output name={key}::{value}") 2025-10-10T00:01:09.0324818Z return 2025-10-10T00:01:09.0325066Z 2025-10-10T00:01:09.0325412Z with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:01:09.0326020Z log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:01:09.0326597Z f.write(f"{key}={value}\n") 2025-10-10T00:01:09.0326923Z 2025-10-10T00:01:09.0326931Z 2025-10-10T00:01:09.0327228Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:01:09.0327869Z return frozenset( 2025-10-10T00:01:09.0328493Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:01:09.0329328Z ) 2025-10-10T00:01:09.0329526Z 2025-10-10T00:01:09.0329533Z 2025-10-10T00:01:09.0329711Z def parse_args() -> Any: 2025-10-10T00:01:09.0330277Z parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:01:09.0331144Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:01:09.0331914Z parser.add_argument( 2025-10-10T00:01:09.0332381Z "--github-issue-repo", 2025-10-10T00:01:09.0332852Z type=str, 2025-10-10T00:01:09.0333253Z required=False, 2025-10-10T00:01:09.0333706Z default="pytorch/test-infra", 2025-10-10T00:01:09.0334243Z help="GitHub repo to get the issue", 2025-10-10T00:01:09.0334748Z ) 2025-10-10T00:01:09.0335112Z parser.add_argument( 2025-10-10T00:01:09.0335563Z "--github-repo", 2025-10-10T00:01:09.0335995Z type=str, 2025-10-10T00:01:09.0336375Z required=True, 2025-10-10T00:01:09.0336829Z help="GitHub repo where CI is running", 2025-10-10T00:01:09.0337330Z ) 2025-10-10T00:01:09.0337693Z parser.add_argument( 2025-10-10T00:01:09.0338300Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:01:09.0338961Z ) 2025-10-10T00:01:09.0339410Z parser.add_argument( 2025-10-10T00:01:09.0340014Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:01:09.0340675Z ) 2025-10-10T00:01:09.0341024Z parser.add_argument( 2025-10-10T00:01:09.0341761Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:01:09.0342435Z ) 2025-10-10T00:01:09.0342797Z parser.add_argument( 2025-10-10T00:01:09.0343447Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:01:09.0344162Z ) 2025-10-10T00:01:09.0344518Z parser.add_argument( 2025-10-10T00:01:09.0344978Z "--github-ref-type", 2025-10-10T00:01:09.0345429Z type=str, 2025-10-10T00:01:09.0345821Z required=True, 2025-10-10T00:01:09.0346317Z help="Current GitHub ref type, branch or tag", 2025-10-10T00:01:09.0346861Z ) 2025-10-10T00:01:09.0347223Z parser.add_argument( 2025-10-10T00:01:09.0347680Z "--eligible-experiments", 2025-10-10T00:01:09.0348202Z type=_str_comma_separated_to_set, 2025-10-10T00:01:09.0348722Z required=False, 2025-10-10T00:01:09.0349138Z default="", 2025-10-10T00:01:09.0350243Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:01:09.0351179Z ) 2025-10-10T00:01:09.0351542Z parser.add_argument( 2025-10-10T00:01:09.0352005Z "--opt-out-experiments", 2025-10-10T00:01:09.0352525Z type=_str_comma_separated_to_set, 2025-10-10T00:01:09.0353043Z required=False, 2025-10-10T00:01:09.0353470Z default="", 2025-10-10T00:01:09.0353858Z help=( 2025-10-10T00:01:09.0354549Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:01:09.0355689Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:01:09.0356529Z ), 2025-10-10T00:01:09.0356886Z ) 2025-10-10T00:01:09.0357245Z parser.add_argument( 2025-10-10T00:01:09.0357685Z "--pr-number", 2025-10-10T00:01:09.0358099Z type=str, 2025-10-10T00:01:09.0358501Z required=False, 2025-10-10T00:01:09.0358922Z default="", 2025-10-10T00:01:09.0359611Z help="the optional PR number where this is run", 2025-10-10T00:01:09.0360184Z ) 2025-10-10T00:01:09.0360391Z 2025-10-10T00:01:09.0360577Z return parser.parse_args() 2025-10-10T00:01:09.0360889Z 2025-10-10T00:01:09.0360896Z 2025-10-10T00:01:09.0361310Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:01:09.0362054Z auth = Auth.Token(github_token) 2025-10-10T00:01:09.0362561Z return Github(auth=auth) 2025-10-10T00:01:09.0362854Z 2025-10-10T00:01:09.0362861Z 2025-10-10T00:01:09.0363319Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:01:09.0364135Z repo = gh.get_repo(repo) 2025-10-10T00:01:09.0364633Z return repo.get_issue(number=issue_num) 2025-10-10T00:01:09.0365012Z 2025-10-10T00:01:09.0365019Z 2025-10-10T00:01:09.0365203Z def get_potential_pr_author( 2025-10-10T00:01:09.0365873Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:01:09.0366545Z ) -> str: 2025-10-10T00:01:09.0367072Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:01:09.0367876Z # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:01:09.0368632Z # embedded in the tag name: ciflow// 2025-10-10T00:01:09.0369056Z 2025-10-10T00:01:09.0369306Z gh = get_gh_client(github_token) 2025-10-10T00:01:09.0369657Z 2025-10-10T00:01:09.0369926Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:01:09.0370553Z split_tag = ref_name.split("/") 2025-10-10T00:01:09.0371057Z if ( 2025-10-10T00:01:09.0371448Z len(split_tag) == 3 2025-10-10T00:01:09.0371918Z and split_tag[0] == "ciflow" 2025-10-10T00:01:09.0372432Z and split_tag[2].isnumeric() 2025-10-10T00:01:09.0372909Z ): 2025-10-10T00:01:09.0373290Z pr_number = split_tag[2] 2025-10-10T00:01:09.0373844Z try: 2025-10-10T00:01:09.0374278Z repository = gh.get_repo(repo) 2025-10-10T00:01:09.0374905Z pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:01:09.0375516Z except Exception as e: 2025-10-10T00:01:09.0376045Z raise Exception( # noqa: TRY002 2025-10-10T00:01:09.0376733Z f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:01:09.0377397Z ) from e 2025-10-10T00:01:09.0377920Z return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:01:09.0378619Z # In all other cases, return the original input username 2025-10-10T00:01:09.0379271Z return username 2025-10-10T00:01:09.0379526Z 2025-10-10T00:01:09.0379533Z 2025-10-10T00:01:09.0379751Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:01:09.0380291Z """ 2025-10-10T00:01:09.0380908Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:01:09.0381685Z """ 2025-10-10T00:01:09.0382212Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:01:09.0382731Z 2025-10-10T00:01:09.0382738Z 2025-10-10T00:01:09.0382928Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:01:09.0383423Z try: 2025-10-10T00:01:09.0383796Z data = yaml.safe_load(yaml_text) 2025-10-10T00:01:09.0384307Z return data 2025-10-10T00:01:09.0384710Z except yaml.YAMLError: 2025-10-10T00:01:09.0385191Z log.exception("Error loading YAML") 2025-10-10T00:01:09.0385692Z raise 2025-10-10T00:01:09.0385915Z 2025-10-10T00:01:09.0385922Z 2025-10-10T00:01:09.0386326Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:01:09.0387058Z """ 2025-10-10T00:01:09.0387666Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:01:09.0388270Z 2025-10-10T00:01:09.0388702Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:09.0389688Z and the text below is the list of opted in users. 2025-10-10T00:01:09.0390112Z 2025-10-10T00:01:09.0390510Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:01:09.0391219Z """ 2025-10-10T00:01:09.0391678Z rollout_state_parts = rollout_state.split("---") 2025-10-10T00:01:09.0392295Z if len(rollout_state_parts) >= 2: 2025-10-10T00:01:09.0392924Z return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:01:09.0393515Z else: 2025-10-10T00:01:09.0393885Z return "", rollout_state 2025-10-10T00:01:09.0394194Z 2025-10-10T00:01:09.0394200Z 2025-10-10T00:01:09.0394408Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:01:09.0394908Z """ 2025-10-10T00:01:09.0395413Z Dictionary of users with a list of features they have opted into 2025-10-10T00:01:09.0396031Z """ 2025-10-10T00:01:09.0396237Z 2025-10-10T00:01:09.0396244Z 2025-10-10T00:01:09.0396586Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:01:09.0397236Z """ 2025-10-10T00:01:09.0397912Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:01:09.0398599Z 2025-10-10T00:01:09.0399318Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:01:09.0400314Z - Example line: "@User1,lf,split_build" 2025-10-10T00:01:09.0401004Z - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:01:09.0401490Z 2025-10-10T00:01:09.0401498Z 2025-10-10T00:01:09.0401658Z """ 2025-10-10T00:01:09.0402017Z optins = UserOptins() 2025-10-10T00:01:09.0402500Z for user in user_optin_text.split("\n"): 2025-10-10T00:01:09.0403059Z user = user.strip("\r\n\t -") 2025-10-10T00:01:09.0403606Z if not user or not user.startswith("@"): 2025-10-10T00:01:09.0404253Z # Not a valid user. Skip 2025-10-10T00:01:09.0404747Z continue 2025-10-10T00:01:09.0404993Z 2025-10-10T00:01:09.0405152Z if user: 2025-10-10T00:01:09.0405592Z usr_name = user.split(",")[0].strip("@") 2025-10-10T00:01:09.0406302Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:01:09.0406801Z 2025-10-10T00:01:09.0406964Z return optins 2025-10-10T00:01:09.0407207Z 2025-10-10T00:01:09.0407214Z 2025-10-10T00:01:09.0407511Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:01:09.0408116Z """ 2025-10-10T00:01:09.0408513Z Check if the experiment name is valid. 2025-10-10T00:01:09.0409023Z A valid name: 2025-10-10T00:01:09.0409733Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:01:09.0410696Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:01:09.0411421Z - Cannot contain spaces 2025-10-10T00:01:09.0411892Z """ 2025-10-10T00:01:09.0412088Z 2025-10-10T00:01:09.0412359Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:01:09.0413081Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:01:09.0413533Z 2025-10-10T00:01:09.0413691Z if valid: 2025-10-10T00:01:09.0414080Z return True 2025-10-10T00:01:09.0414325Z 2025-10-10T00:01:09.0414490Z log.error( 2025-10-10T00:01:09.0415913Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-10-10T00:01:09.0417422Z ) 2025-10-10T00:01:09.0417767Z return False 2025-10-10T00:01:09.0418009Z 2025-10-10T00:01:09.0418016Z 2025-10-10T00:01:09.0418319Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:01:09.0418935Z """ 2025-10-10T00:01:09.0419630Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:01:09.0420366Z """ 2025-10-10T00:01:09.0420706Z try: 2025-10-10T00:01:09.0421081Z if settings_text: 2025-10-10T00:01:09.0421791Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:01:09.0422579Z # for easy reading 2025-10-10T00:01:09.0423389Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:01:09.0424296Z # the backtick character in shell commands. 2025-10-10T00:01:09.0424923Z backtick = chr(96) # backtick character 2025-10-10T00:01:09.0425598Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:01:09.0426271Z settings = load_yaml(settings_text) 2025-10-10T00:01:09.0426628Z 2025-10-10T00:01:09.0427045Z # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:01:09.0427808Z experiments = {} 2025-10-10T00:01:09.0428107Z 2025-10-10T00:01:09.0428496Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:01:09.0429439Z if not is_valid_experiment_name(exp_name): 2025-10-10T00:01:09.0430627Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-10-10T00:01:09.0431653Z continue 2025-10-10T00:01:09.0431960Z 2025-10-10T00:01:09.0432147Z valid_settings = {} 2025-10-10T00:01:09.0432667Z for setting in exp_settings: 2025-10-10T00:01:09.0433254Z if setting not in Experiment._fields: 2025-10-10T00:01:09.0433824Z log.warning( 2025-10-10T00:01:09.0434538Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:01:09.0435348Z ) 2025-10-10T00:01:09.0435770Z else: 2025-10-10T00:01:09.0436277Z valid_settings[setting] = exp_settings[setting] 2025-10-10T00:01:09.0436700Z 2025-10-10T00:01:09.0436991Z experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:01:09.0437632Z return Settings(experiments) 2025-10-10T00:01:09.0437982Z 2025-10-10T00:01:09.0438167Z except Exception: 2025-10-10T00:01:09.0438656Z log.exception("Failed to parse settings") 2025-10-10T00:01:09.0439045Z 2025-10-10T00:01:09.0439284Z return Settings() 2025-10-10T00:01:09.0439535Z 2025-10-10T00:01:09.0439543Z 2025-10-10T00:01:09.0439789Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:01:09.0440353Z """ 2025-10-10T00:01:09.0440775Z Parse settings, if any, from the rollout state. 2025-10-10T00:01:09.0441175Z 2025-10-10T00:01:09.0441532Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:09.0442311Z and the text below is the list of opted in users. 2025-10-10T00:01:09.0442726Z 2025-10-10T00:01:09.0443141Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:01:09.0443873Z """ 2025-10-10T00:01:09.0444425Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:09.0445192Z return parse_settings_from_text(settings_text) 2025-10-10T00:01:09.0445601Z 2025-10-10T00:01:09.0445608Z 2025-10-10T00:01:09.0445864Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:01:09.0446420Z """ 2025-10-10T00:01:09.0446800Z Parse users from the rollout state. 2025-10-10T00:01:09.0447155Z 2025-10-10T00:01:09.0447308Z """ 2025-10-10T00:01:09.0447845Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:09.0448589Z return parse_user_opt_in_from_text(users_text) 2025-10-10T00:01:09.0449003Z 2025-10-10T00:01:09.0449010Z 2025-10-10T00:01:09.0449541Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:09.0450292Z """ 2025-10-10T00:01:09.0450692Z Check if a user is opted into an experiment 2025-10-10T00:01:09.0451227Z """ 2025-10-10T00:01:09.0451674Z return experiment_name in user_optins.get(user, []) 2025-10-10T00:01:09.0452102Z 2025-10-10T00:01:09.0452118Z 2025-10-10T00:01:09.0452542Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:09.0453283Z """ 2025-10-10T00:01:09.0453754Z Check if a user explicitly opted out of an experiment 2025-10-10T00:01:09.0454345Z """ 2025-10-10T00:01:09.0454871Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:01:09.0455543Z experiment_optout = "-" + experiment_name 2025-10-10T00:01:09.0456171Z if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:01:09.0456779Z return False 2025-10-10T00:01:09.0457026Z 2025-10-10T00:01:09.0457302Z if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:01:09.0457902Z log.warning( 2025-10-10T00:01:09.0458706Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:01:09.0459648Z ) 2025-10-10T00:01:09.0459847Z 2025-10-10T00:01:09.0460012Z return True 2025-10-10T00:01:09.0460243Z 2025-10-10T00:01:09.0460249Z 2025-10-10T00:01:09.0460417Z def get_runner_prefix( 2025-10-10T00:01:09.0460854Z rollout_state: str, 2025-10-10T00:01:09.0461303Z workflow_requestors: Iterable[str], 2025-10-10T00:01:09.0461808Z branch: str, 2025-10-10T00:01:09.0462273Z eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:09.0462927Z opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:09.0463516Z is_canary: bool = False, 2025-10-10T00:01:09.0463975Z ) -> str: 2025-10-10T00:01:09.0464460Z settings = parse_settings(rollout_state) 2025-10-10T00:01:09.0465035Z user_optins = parse_users(rollout_state) 2025-10-10T00:01:09.0465410Z 2025-10-10T00:01:09.0465584Z fleet_prefix = "" 2025-10-10T00:01:09.0466000Z prefixes = [] 2025-10-10T00:01:09.0466603Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:01:09.0467502Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:01:09.0468216Z log.info( 2025-10-10T00:01:09.0468905Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:01:09.0469837Z ) 2025-10-10T00:01:09.0470212Z continue 2025-10-10T00:01:09.0470452Z 2025-10-10T00:01:09.0470637Z if opt_out_experiments: 2025-10-10T00:01:09.0471172Z if experiment_name in opt_out_experiments: 2025-10-10T00:01:09.0471788Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:01:09.0472371Z log.info( 2025-10-10T00:01:09.0473257Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:01:09.0474219Z ) 2025-10-10T00:01:09.0474613Z continue 2025-10-10T00:01:09.0474880Z 2025-10-10T00:01:09.0475063Z if eligible_experiments: 2025-10-10T00:01:09.0475666Z if experiment_name not in eligible_experiments: 2025-10-10T00:01:09.0476299Z exp_list = ", ".join(eligible_experiments) 2025-10-10T00:01:09.0476850Z log.info( 2025-10-10T00:01:09.0477631Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:01:09.0478459Z ) 2025-10-10T00:01:09.0478855Z continue 2025-10-10T00:01:09.0479399Z elif not experiment_settings.default: 2025-10-10T00:01:09.0479934Z log.info( 2025-10-10T00:01:09.0480690Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:01:09.0481433Z ) 2025-10-10T00:01:09.0481796Z continue 2025-10-10T00:01:09.0482050Z 2025-10-10T00:01:09.0482319Z # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:01:09.0482932Z opted_out_users = [ 2025-10-10T00:01:09.0483397Z requestor 2025-10-10T00:01:09.0483875Z for requestor in workflow_requestors 2025-10-10T00:01:09.0484563Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:01:09.0485197Z ] 2025-10-10T00:01:09.0485401Z 2025-10-10T00:01:09.0485581Z if opted_out_users: 2025-10-10T00:01:09.0486031Z log.info( 2025-10-10T00:01:09.0486630Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:01:09.0487324Z ) 2025-10-10T00:01:09.0487692Z continue 2025-10-10T00:01:09.0487955Z 2025-10-10T00:01:09.0488239Z # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:01:09.0488862Z opted_in_users = [ 2025-10-10T00:01:09.0489361Z requestor 2025-10-10T00:01:09.0489826Z for requestor in workflow_requestors 2025-10-10T00:01:09.0490492Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:01:09.0491108Z ] 2025-10-10T00:01:09.0491431Z 2025-10-10T00:01:09.0491664Z enabled = False 2025-10-10T00:01:09.0492143Z if opted_in_users: 2025-10-10T00:01:09.0492582Z log.info( 2025-10-10T00:01:09.0493177Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:01:09.0493859Z ) 2025-10-10T00:01:09.0494240Z enabled = True 2025-10-10T00:01:09.0494525Z 2025-10-10T00:01:09.0494748Z elif experiment_settings.rollout_perc: 2025-10-10T00:01:09.0495580Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:01:09.0496574Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:01:09.0497216Z log.info( 2025-10-10T00:01:09.0498084Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:01:09.0498990Z ) 2025-10-10T00:01:09.0499457Z enabled = True 2025-10-10T00:01:09.0499758Z 2025-10-10T00:01:09.0499925Z if enabled: 2025-10-10T00:01:09.0500325Z label = experiment_name 2025-10-10T00:01:09.0500864Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:01:09.0501678Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:01:09.0502564Z # - If it's enabled, then we always list it's prefix first 2025-10-10T00:01:09.0503345Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:01:09.0504012Z if is_canary: 2025-10-10T00:01:09.0504512Z label += CANARY_FLEET_SUFFIX 2025-10-10T00:01:09.0505062Z fleet_prefix = label 2025-10-10T00:01:09.0505552Z else: 2025-10-10T00:01:09.0505972Z prefixes.append(label) 2025-10-10T00:01:09.0506335Z 2025-10-10T00:01:09.0506513Z if len(prefixes) > 1: 2025-10-10T00:01:09.0506942Z log.error( 2025-10-10T00:01:09.0507982Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-10-10T00:01:09.0509074Z ) 2025-10-10T00:01:09.0509503Z prefixes = prefixes[:1] 2025-10-10T00:01:09.0509815Z 2025-10-10T00:01:09.0510007Z # Fleet always comes first 2025-10-10T00:01:09.0510471Z if fleet_prefix: 2025-10-10T00:01:09.0510925Z prefixes.insert(0, fleet_prefix) 2025-10-10T00:01:09.0511282Z 2025-10-10T00:01:09.0511602Z return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:01:09.0512014Z 2025-10-10T00:01:09.0512021Z 2025-10-10T00:01:09.0512463Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:01:09.0513227Z """ 2025-10-10T00:01:09.0513782Z Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:01:09.0514351Z 2025-10-10T00:01:09.0514736Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:01:09.0515425Z """ 2025-10-10T00:01:09.0515813Z gh = get_gh_client(github_token) 2025-10-10T00:01:09.0516359Z issue = get_issue(gh, repo, issue_num) 2025-10-10T00:01:09.0517003Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:01:09.0517446Z 2025-10-10T00:01:09.0517453Z 2025-10-10T00:01:09.0517859Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:01:09.0518609Z for _ in range(num_retries): 2025-10-10T00:01:09.0519083Z try: 2025-10-10T00:01:09.0519547Z req = Request(url=url, headers=headers) 2025-10-10T00:01:09.0520390Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:01:09.0521034Z return json.loads(content) 2025-10-10T00:01:09.0521561Z except Exception as e: 2025-10-10T00:01:09.0522105Z log.warning(f"Could not download {url}: {e}") 2025-10-10T00:01:09.0522498Z 2025-10-10T00:01:09.0522873Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:01:09.0523575Z return {} 2025-10-10T00:01:09.0523793Z 2025-10-10T00:01:09.0523801Z 2025-10-10T00:01:09.0523949Z @cache 2025-10-10T00:01:09.0524571Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:01:09.0525309Z """ 2025-10-10T00:01:09.0525698Z Dynamically get PR information 2025-10-10T00:01:09.0526263Z """ 2025-10-10T00:01:09.0526740Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:01:09.0527356Z headers = { 2025-10-10T00:01:09.0527795Z "Accept": "application/vnd.github.v3+json", 2025-10-10T00:01:09.0528406Z "Authorization": f"token {github_token}", 2025-10-10T00:01:09.0528929Z } 2025-10-10T00:01:09.0529417Z json_response: dict[str, Any] = download_json( 2025-10-10T00:01:09.0530024Z url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:01:09.0530585Z headers=headers, 2025-10-10T00:01:09.0531005Z ) 2025-10-10T00:01:09.0531209Z 2025-10-10T00:01:09.0531387Z if not json_response: 2025-10-10T00:01:09.0531944Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:01:09.0532533Z return {} 2025-10-10T00:01:09.0532769Z 2025-10-10T00:01:09.0532950Z return json_response 2025-10-10T00:01:09.0533219Z 2025-10-10T00:01:09.0533226Z 2025-10-10T00:01:09.0533628Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:01:09.0534359Z """ 2025-10-10T00:01:09.0534863Z Dynamically get the latest list of labels from the pull request 2025-10-10T00:01:09.0535517Z """ 2025-10-10T00:01:09.0535995Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:01:09.0536582Z return { 2025-10-10T00:01:09.0537161Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:01:09.0537843Z } 2025-10-10T00:01:09.0538048Z 2025-10-10T00:01:09.0538055Z 2025-10-10T00:01:09.0538215Z def main() -> None: 2025-10-10T00:01:09.0538613Z args = parse_args() 2025-10-10T00:01:09.0538878Z 2025-10-10T00:01:09.0539091Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:01:09.0539518Z 2025-10-10T00:01:09.0539708Z # Check if the PR is opt-out 2025-10-10T00:01:09.0540171Z if args.pr_number: 2025-10-10T00:01:09.0540802Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:01:09.0541588Z if OPT_OUT_LABEL in labels: 2025-10-10T00:01:09.0542069Z log.info( 2025-10-10T00:01:09.0542724Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:01:09.0543456Z ) 2025-10-10T00:01:09.0543981Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:09.0544625Z sys.exit() 2025-10-10T00:01:09.0544876Z 2025-10-10T00:01:09.0545046Z try: 2025-10-10T00:01:09.0545467Z rollout_state = get_rollout_state_from_issue( 2025-10-10T00:01:09.0546159Z args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:01:09.0546762Z ) 2025-10-10T00:01:09.0546968Z 2025-10-10T00:01:09.0547162Z username = get_potential_pr_author( 2025-10-10T00:01:09.0547675Z args.github_token, 2025-10-10T00:01:09.0548140Z args.github_repo, 2025-10-10T00:01:09.0548602Z args.github_actor, 2025-10-10T00:01:09.0549066Z args.github_ref_type, 2025-10-10T00:01:09.0549611Z args.github_branch, 2025-10-10T00:01:09.0550053Z ) 2025-10-10T00:01:09.0550250Z 2025-10-10T00:01:09.0550535Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:01:09.0550965Z 2025-10-10T00:01:09.0551175Z runner_label_prefix = get_runner_prefix( 2025-10-10T00:01:09.0551704Z rollout_state, 2025-10-10T00:01:09.0552167Z (args.github_issue_owner, username), 2025-10-10T00:01:09.0552691Z args.github_branch, 2025-10-10T00:01:09.0553170Z args.eligible_experiments, 2025-10-10T00:01:09.0553682Z args.opt_out_experiments, 2025-10-10T00:01:09.0554170Z is_canary, 2025-10-10T00:01:09.0554589Z ) 2025-10-10T00:01:09.0554783Z 2025-10-10T00:01:09.0554968Z except Exception as e: 2025-10-10T00:01:09.0555396Z log.error( 2025-10-10T00:01:09.0556038Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:01:09.0556833Z ) 2025-10-10T00:01:09.0557039Z 2025-10-10T00:01:09.0557353Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:09.0557826Z 2025-10-10T00:01:09.0557832Z 2025-10-10T00:01:09.0558013Z if __name__ == "__main__": 2025-10-10T00:01:09.0558436Z main() 2025-10-10T00:01:09.0558641Z 2025-10-10T00:01:09.0642993Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:01:09.0643867Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:01:09.0662354Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:09.0662835Z env: 2025-10-10T00:01:09.0663463Z GITHUB_TOKEN: *** 2025-10-10T00:01:09.0663864Z ISSUE_NUMBER: 5132 2025-10-10T00:01:09.0664314Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:09.0664811Z ISSUE_OWNER: 2025-10-10T00:01:09.0665204Z CHECK_EXPERIMENTS: 2025-10-10T00:01:09.0665656Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:01:09.0666099Z PR_NUMBER: 2025-10-10T00:01:09.0666472Z ##[endgroup] 2025-10-10T00:01:09.6625721Z Defaulting to user installation because normal site-packages is not writeable 2025-10-10T00:01:10.4768821Z Collecting urllib3==1.26.18 2025-10-10T00:01:10.5726964Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-10-10T00:01:10.6052769Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.0 MB/s eta 0:00:00 2025-10-10T00:01:10.6442635Z Collecting PyGithub==2.3.0 2025-10-10T00:01:10.6697055Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-10-10T00:01:10.7266844Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-10-10T00:01:10.7471363Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-10-10T00:01:10.7519099Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-10-10T00:01:10.7535805Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-10-10T00:01:10.7549657Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-10-10T00:01:10.7932347Z Collecting Deprecated (from PyGithub==2.3.0) 2025-10-10T00:01:10.8133196Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-10-10T00:01:10.8338871Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-10-10T00:01:10.9848922Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:01:11.0037051Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-10-10T00:01:11.1391182Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-10-10T00:01:11.1579812Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-10-10T00:01:11.1911597Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:01:11.2097049Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-10-10T00:01:11.2452704Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-10-10T00:01:11.2687284Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 6.2 MB/s eta 0:00:00 2025-10-10T00:01:11.2914978Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-10-10T00:01:11.3132890Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 17.1 MB/s eta 0:00:00 2025-10-10T00:01:11.3319795Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-10-10T00:01:11.3602911Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 52.1 MB/s eta 0:00:00 2025-10-10T00:01:11.3787879Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-10-10T00:01:11.4001917Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-10-10T00:01:11.4048902Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 70.7 MB/s eta 0:00:00 2025-10-10T00:01:11.4236549Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-10-10T00:01:11.4275621Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 37.2 MB/s eta 0:00:00 2025-10-10T00:01:11.4464374Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-10-10T00:01:11.4503564Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 46.8 MB/s eta 0:00:00 2025-10-10T00:01:11.7138776Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-10-10T00:01:12.2077925Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.0 urllib3-1.26.18 wrapt-1.17.3 2025-10-10T00:01:12.2874087Z ##[group]Run curr_branch="main" 2025-10-10T00:01:12.2874377Z curr_branch="main" 2025-10-10T00:01:12.2874590Z curr_ref_type="branch" 2025-10-10T00:01:12.2874838Z echo "Current branch is '$curr_branch'" 2025-10-10T00:01:12.2875101Z  2025-10-10T00:01:12.2875290Z python3 runner_determinator.py \ 2025-10-10T00:01:12.2875556Z  --github-token "$GITHUB_TOKEN" \ 2025-10-10T00:01:12.2875813Z  --github-issue "$ISSUE_NUMBER" \ 2025-10-10T00:01:12.2876075Z  --github-branch "$curr_branch" \ 2025-10-10T00:01:12.2876325Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-10-10T00:01:12.2876583Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-10-10T00:01:12.2876847Z  --github-ref-type "$curr_ref_type" \ 2025-10-10T00:01:12.2877094Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-10-10T00:01:12.2877381Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-10-10T00:01:12.2877720Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-10-10T00:01:12.2877997Z  --pr-number "${PR_NUMBER}" 2025-10-10T00:01:12.2898427Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:12.2898652Z env: 2025-10-10T00:01:12.2899388Z GITHUB_TOKEN: *** 2025-10-10T00:01:12.2899569Z ISSUE_NUMBER: 5132 2025-10-10T00:01:12.2899765Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:12.2899988Z ISSUE_OWNER: 2025-10-10T00:01:12.2900151Z CHECK_EXPERIMENTS: 2025-10-10T00:01:12.2900339Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:01:12.2900513Z PR_NUMBER: 2025-10-10T00:01:12.2900677Z ##[endgroup] 2025-10-10T00:01:12.2937498Z Current branch is 'main' 2025-10-10T00:01:13.7836829Z INFO : Based on rollout percentage of 60%, enabling experiment lf. 2025-10-10T00:01:13.7837934Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-10-10T00:01:13.7839739Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-10-10T00:01:13.7841377Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-10-10T00:01:13.7842648Z INFO : Setting output: label-type='lf.' 2025-10-10T00:01:13.8192709Z Evaluate and set job outputs 2025-10-10T00:01:13.8199438Z Set output 'label-type' 2025-10-10T00:01:13.8201383Z Cleaning up orphan processes