2025-08-26T19:31:43.9285468Z Current runner version: '2.328.0' 2025-08-26T19:31:43.9307318Z ##[group]Runner Image Provisioner 2025-08-26T19:31:43.9308291Z Hosted Compute Agent 2025-08-26T19:31:43.9308808Z Version: 20250825.382 2025-08-26T19:31:43.9309462Z Commit: 7109f2abf901479dac39397456e363ac0cca0730 2025-08-26T19:31:43.9310443Z Build Date: 2025-08-25T22:55:50Z 2025-08-26T19:31:43.9311025Z ##[endgroup] 2025-08-26T19:31:43.9311647Z ##[group]Operating System 2025-08-26T19:31:43.9312176Z Ubuntu 2025-08-26T19:31:43.9312654Z 24.04.2 2025-08-26T19:31:43.9313114Z LTS 2025-08-26T19:31:43.9313587Z ##[endgroup] 2025-08-26T19:31:43.9314097Z ##[group]Runner Image 2025-08-26T19:31:43.9314640Z Image: ubuntu-24.04 2025-08-26T19:31:43.9315222Z Version: 20250818.1.0 2025-08-26T19:31:43.9316190Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250818.1/images/ubuntu/Ubuntu2404-Readme.md 2025-08-26T19:31:43.9317813Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250818.1 2025-08-26T19:31:43.9318765Z ##[endgroup] 2025-08-26T19:31:43.9319805Z ##[group]GITHUB_TOKEN Permissions 2025-08-26T19:31:43.9322132Z Contents: read 2025-08-26T19:31:43.9322675Z Metadata: read 2025-08-26T19:31:43.9323263Z ##[endgroup] 2025-08-26T19:31:43.9325151Z Secret source: Actions 2025-08-26T19:31:43.9325856Z Prepare workflow directory 2025-08-26T19:31:43.9830394Z Prepare all required actions 2025-08-26T19:31:43.9886343Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (262640fd220236042fbf4443cc163c8838c84c3d) 2025-08-26T19:31:43.9891268Z ##[group] Inputs 2025-08-26T19:31:43.9891854Z check_experiments: 2025-08-26T19:31:43.9892357Z opt_out_experiments: lf 2025-08-26T19:31:43.9893089Z triggering_actor: pytorchmergebot 2025-08-26T19:31:43.9893690Z issue_owner: 2025-08-26T19:31:43.9894136Z curr_branch: main 2025-08-26T19:31:43.9894732Z curr_ref_type: branch 2025-08-26T19:31:43.9895316Z issue_number: 5132 2025-08-26T19:31:43.9895847Z ##[endgroup] 2025-08-26T19:31:43.9896557Z Complete job name: unit-test / get-label-type / runner-determinator 2025-08-26T19:31:44.7061805Z ##[group]Run cat < runner_determinator.py 2025-08-26T19:31:44.7064370Z cat < runner_determinator.py 2025-08-26T19:31:44.7065038Z # flake8: noqa: G004 2025-08-26T19:31:44.7065634Z  2025-08-26T19:31:44.7066353Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:31:44.7067468Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:31:44.7068380Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:31:44.7069104Z  2025-08-26T19:31:44.7069536Z """ 2025-08-26T19:31:44.7070402Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:31:44.7071442Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:31:44.7072579Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:31:44.7073649Z of which runners should be used to run which job. 2025-08-26T19:31:44.7074286Z  2025-08-26T19:31:44.7074981Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:31:44.7076039Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:31:44.7077043Z settings are considered to be empty with only the second part, the user 2025-08-26T19:31:44.7077869Z list, defined. 2025-08-26T19:31:44.7078393Z  2025-08-26T19:31:44.7079059Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:31:44.7080186Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:31:44.7081230Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:31:44.7081939Z  2025-08-26T19:31:44.7082853Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:31:44.7083927Z The user list is also a comma separated list of additional features or 2025-08-26T19:31:44.7084769Z experiments which the user could be opted in to. 2025-08-26T19:31:44.7085428Z  2025-08-26T19:31:44.7085962Z The user list has the following rules: 2025-08-26T19:31:44.7086576Z  2025-08-26T19:31:44.7087202Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:31:44.7088172Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:31:44.7089080Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:31:44.7089684Z  2025-08-26T19:31:44.7090398Z Example config: 2025-08-26T19:31:44.7091086Z  # A list of experiments that can be opted into. 2025-08-26T19:31:44.7091856Z  # This defines the behavior they'll induce when opted into. 2025-08-26T19:31:44.7092759Z  # Expected syntax is: 2025-08-26T19:31:44.7093601Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:31:44.7094697Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:31:44.7170332Z  2025-08-26T19:31:44.7170801Z  experiments: 2025-08-26T19:31:44.7171244Z  lf: 2025-08-26T19:31:44.7171670Z  rollout_percent: 25 2025-08-26T19:31:44.7172175Z  all_branches: false 2025-08-26T19:31:44.7172654Z  default: true 2025-08-26T19:31:44.7173097Z  --- 2025-08-26T19:31:44.7173474Z  2025-08-26T19:31:44.7173820Z  # Opt-ins: 2025-08-26T19:31:44.7174455Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:31:44.7175584Z  # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:31:44.7176427Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:31:44.7177126Z  # Experiments should be from the above list. 2025-08-26T19:31:44.7177673Z  2025-08-26T19:31:44.7178047Z  @User1,-lf,split_build 2025-08-26T19:31:44.7178530Z  @User2,lf 2025-08-26T19:31:44.7178954Z  @User3,split_build 2025-08-26T19:31:44.7179393Z """ 2025-08-26T19:31:44.7179738Z  2025-08-26T19:31:44.7180340Z import json 2025-08-26T19:31:44.7180798Z import logging 2025-08-26T19:31:44.7181206Z import os 2025-08-26T19:31:44.7181592Z import random 2025-08-26T19:31:44.7181996Z import re 2025-08-26T19:31:44.7182371Z import sys 2025-08-26T19:31:44.7182858Z from argparse import ArgumentParser 2025-08-26T19:31:44.7183488Z from collections.abc import Iterable 2025-08-26T19:31:44.7184035Z from functools import cache 2025-08-26T19:31:44.7184540Z from logging import LogRecord 2025-08-26T19:31:44.7185069Z from typing import Any, NamedTuple 2025-08-26T19:31:44.7185655Z from urllib.request import Request, urlopen 2025-08-26T19:31:44.7186186Z  2025-08-26T19:31:44.7186535Z import yaml 2025-08-26T19:31:44.7186955Z from github import Auth, Github 2025-08-26T19:31:44.7187481Z from github.Issue import Issue 2025-08-26T19:31:44.7187956Z  2025-08-26T19:31:44.7188287Z  2025-08-26T19:31:44.7188709Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:31:44.7189429Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:31:44.7190621Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:31:44.7191324Z  2025-08-26T19:31:44.7191956Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:31:44.7192555Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:31:44.7193097Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:31:44.7193692Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:31:44.7194204Z  2025-08-26T19:31:44.7194595Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:31:44.7195088Z  2025-08-26T19:31:44.7195456Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:31:44.7195944Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:31:44.7196407Z  2025-08-26T19:31:44.7196734Z  2025-08-26T19:31:44.7197099Z class Experiment(NamedTuple): 2025-08-26T19:31:44.7197611Z  rollout_perc: float = ( 2025-08-26T19:31:44.7198294Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:31:44.7198967Z  ) 2025-08-26T19:31:44.7199346Z  all_branches: bool = ( 2025-08-26T19:31:44.7200136Z  False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:31:44.7200831Z  ) 2025-08-26T19:31:44.7201203Z  default: bool = ( 2025-08-26T19:31:44.7201822Z  True # If True, the experiment is enabled by default for all queries 2025-08-26T19:31:44.7202458Z  ) 2025-08-26T19:31:44.7202807Z  2025-08-26T19:31:44.7203171Z  # Add more fields as needed 2025-08-26T19:31:44.7203643Z  2025-08-26T19:31:44.7203966Z  2025-08-26T19:31:44.7204326Z class Settings(NamedTuple): 2025-08-26T19:31:44.7204792Z  """ 2025-08-26T19:31:44.7205279Z  Settings for the experiments that can be opted into. 2025-08-26T19:31:44.7205864Z  """ 2025-08-26T19:31:44.7206214Z  2025-08-26T19:31:44.7206617Z  experiments: dict[str, Experiment] = {} 2025-08-26T19:31:44.7207129Z  2025-08-26T19:31:44.7207576Z  2025-08-26T19:31:44.7207985Z class ColorFormatter(logging.Formatter): 2025-08-26T19:31:44.7208638Z  """Color codes the log messages based on the log level""" 2025-08-26T19:31:44.7209220Z  2025-08-26T19:31:44.7209561Z  COLORS = { 2025-08-26T19:31:44.7210094Z  "WARNING": "\033[33m", # Yellow 2025-08-26T19:31:44.7210618Z  "ERROR": "\033[31m", # Red 2025-08-26T19:31:44.7211125Z  "CRITICAL": "\033[31m", # Red 2025-08-26T19:31:44.7211647Z  "INFO": "\033[0m", # Reset 2025-08-26T19:31:44.7212152Z  "DEBUG": "\033[0m", # Reset 2025-08-26T19:31:44.7212631Z  } 2025-08-26T19:31:44.7212979Z  2025-08-26T19:31:44.7213392Z  def format(self, record: LogRecord) -> str: 2025-08-26T19:31:44.7214153Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:31:44.7214943Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:31:44.7215529Z  return super().format(record) 2025-08-26T19:31:44.7216021Z  2025-08-26T19:31:44.7216346Z  2025-08-26T19:31:44.7216734Z handler = logging.StreamHandler() 2025-08-26T19:31:44.7217490Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:31:44.7218197Z  2025-08-26T19:31:44.7218652Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:31:44.7219250Z log.addHandler(handler) 2025-08-26T19:31:44.7219729Z log.setLevel(logging.INFO) 2025-08-26T19:31:44.7220456Z  2025-08-26T19:31:44.7220789Z  2025-08-26T19:31:44.7221247Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:31:44.7221840Z  """ 2025-08-26T19:31:44.7222386Z  Defines outputs of the github action that invokes this script 2025-08-26T19:31:44.7223164Z  """ 2025-08-26T19:31:44.7223548Z  if not GITHUB_OUTPUT: 2025-08-26T19:31:44.7224644Z  # 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.7225751Z  log.warning( 2025-08-26T19:31:44.7226631Z  "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.7227531Z  ) 2025-08-26T19:31:44.7227996Z  print(f"::set-output name={key}::{value}") 2025-08-26T19:31:44.7228537Z  return 2025-08-26T19:31:44.7228947Z  2025-08-26T19:31:44.7229339Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:31:44.7229937Z  log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:31:44.7230634Z  f.write(f"{key}={value}\n") 2025-08-26T19:31:44.7231131Z  2025-08-26T19:31:44.7231474Z  2025-08-26T19:31:44.7231982Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:31:44.7232644Z  return frozenset( 2025-08-26T19:31:44.7233294Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:31:44.7233976Z  ) 2025-08-26T19:31:44.7234325Z  2025-08-26T19:31:44.7234656Z  2025-08-26T19:31:44.7235005Z def parse_args() -> Any: 2025-08-26T19:31:44.7235603Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:31:44.7236488Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:31:44.7237248Z  parser.add_argument( 2025-08-26T19:31:44.7237740Z  "--github-issue-repo", 2025-08-26T19:31:44.7238226Z  type=str, 2025-08-26T19:31:44.7238714Z  required=False, 2025-08-26T19:31:44.7239318Z  default="pytorch/test-infra", 2025-08-26T19:31:44.7239893Z  help="GitHub repo to get the issue", 2025-08-26T19:31:44.7240514Z  ) 2025-08-26T19:31:44.7240890Z  parser.add_argument( 2025-08-26T19:31:44.7241357Z  "--github-repo", 2025-08-26T19:31:44.7241813Z  type=str, 2025-08-26T19:31:44.7242245Z  required=True, 2025-08-26T19:31:44.7242747Z  help="GitHub repo where CI is running", 2025-08-26T19:31:44.7243269Z  ) 2025-08-26T19:31:44.7243649Z  parser.add_argument( 2025-08-26T19:31:44.7244281Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:31:44.7244941Z  ) 2025-08-26T19:31:44.7245342Z  parser.add_argument( 2025-08-26T19:31:44.7246007Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:31:44.7246676Z  ) 2025-08-26T19:31:44.7247048Z  parser.add_argument( 2025-08-26T19:31:44.7247718Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:31:44.7248412Z  ) 2025-08-26T19:31:44.7248799Z  parser.add_argument( 2025-08-26T19:31:44.7249502Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:31:44.7250312Z  ) 2025-08-26T19:31:44.7250689Z  parser.add_argument( 2025-08-26T19:31:44.7251164Z  "--github-ref-type", 2025-08-26T19:31:44.7251636Z  type=str, 2025-08-26T19:31:44.7252062Z  required=True, 2025-08-26T19:31:44.7252604Z  help="Current GitHub ref type, branch or tag", 2025-08-26T19:31:44.7253153Z  ) 2025-08-26T19:31:44.7253533Z  parser.add_argument( 2025-08-26T19:31:44.7254165Z  "--eligible-experiments", 2025-08-26T19:31:44.7254712Z  type=_str_comma_separated_to_set, 2025-08-26T19:31:44.7255236Z  required=False, 2025-08-26T19:31:44.7255684Z  default="", 2025-08-26T19:31:44.7256567Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:31:44.7257477Z  ) 2025-08-26T19:31:44.7257854Z  parser.add_argument( 2025-08-26T19:31:44.7258336Z  "--opt-out-experiments", 2025-08-26T19:31:44.7258872Z  type=_str_comma_separated_to_set, 2025-08-26T19:31:44.7259394Z  required=False, 2025-08-26T19:31:44.7259839Z  default="", 2025-08-26T19:31:44.7260549Z  help=( 2025-08-26T19:31:44.7261266Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:31:44.7262408Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:31:44.7263226Z  ), 2025-08-26T19:31:44.7263600Z  ) 2025-08-26T19:31:44.7264012Z  parser.add_argument( 2025-08-26T19:31:44.7264475Z  "--pr-number", 2025-08-26T19:31:44.7265116Z  type=str, 2025-08-26T19:31:44.7265778Z  required=False, 2025-08-26T19:31:44.7266535Z  default="", 2025-08-26T19:31:44.7267323Z  help="the optional PR number where this is run", 2025-08-26T19:31:44.7267928Z  ) 2025-08-26T19:31:44.7268494Z  2025-08-26T19:31:44.7268982Z  return parser.parse_args() 2025-08-26T19:31:44.7269503Z  2025-08-26T19:31:44.7269827Z  2025-08-26T19:31:44.7270718Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.7271679Z  auth = Auth.Token(github_token) 2025-08-26T19:31:44.7272213Z  return Github(auth=auth) 2025-08-26T19:31:44.7272678Z  2025-08-26T19:31:44.7273004Z  2025-08-26T19:31:44.7273655Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.7274451Z  repo = gh.get_repo(repo) 2025-08-26T19:31:44.7274999Z  return repo.get_issue(number=issue_num) 2025-08-26T19:31:44.7275507Z  2025-08-26T19:31:44.7275831Z  2025-08-26T19:31:44.7276194Z def get_potential_pr_author( 2025-08-26T19:31:44.7276871Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:31:44.7277537Z ) -> str: 2025-08-26T19:31:44.7278091Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:31:44.7278914Z  # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:31:44.7279694Z  # embedded in the tag name: ciflow// 2025-08-26T19:31:44.7280381Z  2025-08-26T19:31:44.7280768Z  gh = get_gh_client(github_token) 2025-08-26T19:31:44.7281257Z  2025-08-26T19:31:44.7281730Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:31:44.7282371Z  split_tag = ref_name.split("/") 2025-08-26T19:31:44.7282880Z  if ( 2025-08-26T19:31:44.7283290Z  len(split_tag) == 3 2025-08-26T19:31:44.7283797Z  and split_tag[0] == "ciflow" 2025-08-26T19:31:44.7284343Z  and split_tag[2].isnumeric() 2025-08-26T19:31:44.7284837Z  ): 2025-08-26T19:31:44.7285254Z  pr_number = split_tag[2] 2025-08-26T19:31:44.7285748Z  try: 2025-08-26T19:31:44.7286218Z  repository = gh.get_repo(repo) 2025-08-26T19:31:44.7286982Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:31:44.7287601Z  except Exception as e: 2025-08-26T19:31:44.7288156Z  raise Exception( # noqa: TRY002 2025-08-26T19:31:44.7288836Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:31:44.7289488Z  ) from e 2025-08-26T19:31:44.7290289Z  return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:31:44.7291029Z  # In all other cases, return the original input username 2025-08-26T19:31:44.7291627Z  return username 2025-08-26T19:31:44.7292039Z  2025-08-26T19:31:44.7292369Z  2025-08-26T19:31:44.7292788Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:31:44.7293319Z  """ 2025-08-26T19:31:44.7293989Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:31:44.7294774Z  """ 2025-08-26T19:31:44.7295343Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:31:44.7296002Z  2025-08-26T19:31:44.7296335Z  2025-08-26T19:31:44.7296713Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:31:44.7297224Z  try: 2025-08-26T19:31:44.7297627Z  data = yaml.safe_load(yaml_text) 2025-08-26T19:31:44.7298141Z  return data 2025-08-26T19:31:44.7298585Z  except yaml.YAMLError: 2025-08-26T19:31:44.7299115Z  log.exception("Error loading YAML") 2025-08-26T19:31:44.7299628Z  raise 2025-08-26T19:31:44.7300263Z  2025-08-26T19:31:44.7300648Z  2025-08-26T19:31:44.7301267Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:31:44.7302004Z  """ 2025-08-26T19:31:44.7302800Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:31:44.7303543Z  2025-08-26T19:31:44.7304090Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.7304855Z  and the text below is the list of opted in users. 2025-08-26T19:31:44.7305407Z  2025-08-26T19:31:44.7305971Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:31:44.7306655Z  """ 2025-08-26T19:31:44.7307122Z  rollout_state_parts = rollout_state.split("---") 2025-08-26T19:31:44.7307731Z  if len(rollout_state_parts) >= 2: 2025-08-26T19:31:44.7308359Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:31:44.7309069Z  else: 2025-08-26T19:31:44.7309581Z  return "", rollout_state 2025-08-26T19:31:44.7310509Z  2025-08-26T19:31:44.7310846Z  2025-08-26T19:31:44.7311246Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:31:44.7311758Z  """ 2025-08-26T19:31:44.7312296Z  Dictionary of users with a list of features they have opted into 2025-08-26T19:31:44.7312927Z  """ 2025-08-26T19:31:44.7313273Z  2025-08-26T19:31:44.7313591Z  2025-08-26T19:31:44.7314116Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:31:44.7314763Z  """ 2025-08-26T19:31:44.7315490Z  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.7316292Z  2025-08-26T19:31:44.7317083Z  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.7318072Z  - Example line: "@User1,lf,split_build" 2025-08-26T19:31:44.7318913Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:31:44.7319530Z  2025-08-26T19:31:44.7319845Z  2025-08-26T19:31:44.7320296Z  """ 2025-08-26T19:31:44.7320681Z  optins = UserOptins() 2025-08-26T19:31:44.7321211Z  for user in user_optin_text.split("\n"): 2025-08-26T19:31:44.7321773Z  user = user.strip("\r\n\t -") 2025-08-26T19:31:44.7322325Z  if not user or not user.startswith("@"): 2025-08-26T19:31:44.7322878Z  # Not a valid user. Skip 2025-08-26T19:31:44.7323378Z  continue 2025-08-26T19:31:44.7323797Z  2025-08-26T19:31:44.7324159Z  if user: 2025-08-26T19:31:44.7324631Z  usr_name = user.split(",")[0].strip("@") 2025-08-26T19:31:44.7325335Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:31:44.7325971Z  2025-08-26T19:31:44.7326321Z  return optins 2025-08-26T19:31:44.7326724Z  2025-08-26T19:31:44.7327041Z  2025-08-26T19:31:44.7327531Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:31:44.7328143Z  """ 2025-08-26T19:31:44.7328554Z  Check if the experiment name is valid. 2025-08-26T19:31:44.7329070Z  A valid name: 2025-08-26T19:31:44.7329738Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:31:44.7330781Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:31:44.7331484Z  - Cannot contain spaces 2025-08-26T19:31:44.7331951Z  """ 2025-08-26T19:31:44.7332300Z  2025-08-26T19:31:44.7332752Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:31:44.7333481Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:31:44.7334190Z  2025-08-26T19:31:44.7334776Z  if valid: 2025-08-26T19:31:44.7335176Z  return True 2025-08-26T19:31:44.7335577Z  2025-08-26T19:31:44.7335908Z  log.error( 2025-08-26T19:31:44.7337349Z  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.7338854Z  ) 2025-08-26T19:31:44.7339239Z  return False 2025-08-26T19:31:44.7339630Z  2025-08-26T19:31:44.7339952Z  2025-08-26T19:31:44.7340746Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:31:44.7341371Z  """ 2025-08-26T19:31:44.7341972Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:31:44.7342685Z  """ 2025-08-26T19:31:44.7343039Z  try: 2025-08-26T19:31:44.7343410Z  if settings_text: 2025-08-26T19:31:44.7344150Z  # 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.7344923Z  # for easy reading 2025-08-26T19:31:44.7345780Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:31:44.7346665Z  # the backtick character in shell commands. 2025-08-26T19:31:44.7347277Z  backtick = chr(96) # backtick character 2025-08-26T19:31:44.7347955Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:31:44.7348625Z  settings = load_yaml(settings_text) 2025-08-26T19:31:44.7349127Z  2025-08-26T19:31:44.7349707Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:31:44.7350687Z  experiments = {} 2025-08-26T19:31:44.7351135Z  2025-08-26T19:31:44.7351695Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:31:44.7352465Z  if not is_valid_experiment_name(exp_name): 2025-08-26T19:31:44.7353541Z  # 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.7354557Z  continue 2025-08-26T19:31:44.7354994Z  2025-08-26T19:31:44.7355355Z  valid_settings = {} 2025-08-26T19:31:44.7355888Z  for setting in exp_settings: 2025-08-26T19:31:44.7356455Z  if setting not in Experiment._fields: 2025-08-26T19:31:44.7357018Z  log.warning( 2025-08-26T19:31:44.7357749Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:31:44.7358451Z  ) 2025-08-26T19:31:44.7358885Z  else: 2025-08-26T19:31:44.7359426Z  valid_settings[setting] = exp_settings[setting] 2025-08-26T19:31:44.7360082Z  2025-08-26T19:31:44.7360541Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:31:44.7361182Z  return Settings(experiments) 2025-08-26T19:31:44.7361671Z  2025-08-26T19:31:44.7362012Z  except Exception: 2025-08-26T19:31:44.7362527Z  log.exception("Failed to parse settings") 2025-08-26T19:31:44.7363058Z  2025-08-26T19:31:44.7363399Z  return Settings() 2025-08-26T19:31:44.7363815Z  2025-08-26T19:31:44.7364144Z  2025-08-26T19:31:44.7364705Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:31:44.7365275Z  """ 2025-08-26T19:31:44.7365728Z  Parse settings, if any, from the rollout state. 2025-08-26T19:31:44.7366268Z  2025-08-26T19:31:44.7366803Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.7367575Z  and the text below is the list of opted in users. 2025-08-26T19:31:44.7368119Z  2025-08-26T19:31:44.7368704Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:31:44.7369414Z  """ 2025-08-26T19:31:44.7370075Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.7370844Z  return parse_settings_from_text(settings_text) 2025-08-26T19:31:44.7371379Z  2025-08-26T19:31:44.7371698Z  2025-08-26T19:31:44.7372140Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:31:44.7372694Z  """ 2025-08-26T19:31:44.7373125Z  Parse users from the rollout state. 2025-08-26T19:31:44.7373607Z  2025-08-26T19:31:44.7373930Z  """ 2025-08-26T19:31:44.7374480Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.7375215Z  return parse_user_opt_in_from_text(users_text) 2025-08-26T19:31:44.7375742Z  2025-08-26T19:31:44.7376067Z  2025-08-26T19:31:44.7376697Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.7377415Z  """ 2025-08-26T19:31:44.7377854Z  Check if a user is opted into an experiment 2025-08-26T19:31:44.7378378Z  """ 2025-08-26T19:31:44.7378854Z  return experiment_name in user_optins.get(user, []) 2025-08-26T19:31:44.7379415Z  2025-08-26T19:31:44.7379862Z  2025-08-26T19:31:44.7380747Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.7381486Z  """ 2025-08-26T19:31:44.7381967Z  Check if a user explicitly opted out of an experiment 2025-08-26T19:31:44.7382529Z  """ 2025-08-26T19:31:44.7383045Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:31:44.7383737Z  experiment_optout = "-" + experiment_name 2025-08-26T19:31:44.7384390Z  if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:31:44.7384994Z  return False 2025-08-26T19:31:44.7385405Z  2025-08-26T19:31:44.7385875Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:31:44.7386461Z  log.warning( 2025-08-26T19:31:44.7387287Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:31:44.7388142Z  ) 2025-08-26T19:31:44.7388500Z  2025-08-26T19:31:44.7388837Z  return True 2025-08-26T19:31:44.7389227Z  2025-08-26T19:31:44.7389551Z  2025-08-26T19:31:44.7389896Z def get_runner_prefix( 2025-08-26T19:31:44.7390465Z  rollout_state: str, 2025-08-26T19:31:44.7390956Z  workflow_requestors: Iterable[str], 2025-08-26T19:31:44.7391468Z  branch: str, 2025-08-26T19:31:44.7391991Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.7392685Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.7393269Z  is_canary: bool = False, 2025-08-26T19:31:44.7393722Z ) -> str: 2025-08-26T19:31:44.7394161Z  settings = parse_settings(rollout_state) 2025-08-26T19:31:44.7394746Z  user_optins = parse_users(rollout_state) 2025-08-26T19:31:44.7395263Z  2025-08-26T19:31:44.7395741Z  fleet_prefix = "" 2025-08-26T19:31:44.7396195Z  prefixes = [] 2025-08-26T19:31:44.7396841Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:31:44.7397785Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:31:44.7398490Z  log.info( 2025-08-26T19:31:44.7399185Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:31:44.7399918Z  ) 2025-08-26T19:31:44.7400422Z  continue 2025-08-26T19:31:44.7400838Z  2025-08-26T19:31:44.7401205Z  if opt_out_experiments: 2025-08-26T19:31:44.7401760Z  if experiment_name in opt_out_experiments: 2025-08-26T19:31:44.7402406Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:31:44.7403019Z  log.info( 2025-08-26T19:31:44.7403962Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:31:44.7404900Z  ) 2025-08-26T19:31:44.7405354Z  continue 2025-08-26T19:31:44.7405782Z  2025-08-26T19:31:44.7406133Z  if eligible_experiments: 2025-08-26T19:31:44.7406712Z  if experiment_name not in eligible_experiments: 2025-08-26T19:31:44.7407340Z  exp_list = ", ".join(eligible_experiments) 2025-08-26T19:31:44.7407880Z  log.info( 2025-08-26T19:31:44.7408657Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:31:44.7409462Z  ) 2025-08-26T19:31:44.7410095Z  continue 2025-08-26T19:31:44.7410596Z  elif not experiment_settings.default: 2025-08-26T19:31:44.7411123Z  log.info( 2025-08-26T19:31:44.7411797Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:31:44.7412511Z  ) 2025-08-26T19:31:44.7412900Z  continue 2025-08-26T19:31:44.7413310Z  2025-08-26T19:31:44.7413772Z  # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:31:44.7414382Z  opted_out_users = [ 2025-08-26T19:31:44.7414847Z  requestor 2025-08-26T19:31:44.7415333Z  for requestor in workflow_requestors 2025-08-26T19:31:44.7416017Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.7416630Z  ] 2025-08-26T19:31:44.7416994Z  2025-08-26T19:31:44.7417353Z  if opted_out_users: 2025-08-26T19:31:44.7417811Z  log.info( 2025-08-26T19:31:44.7418448Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:31:44.7419122Z  ) 2025-08-26T19:31:44.7419512Z  continue 2025-08-26T19:31:44.7419915Z  2025-08-26T19:31:44.7420475Z  # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:31:44.7421087Z  opted_in_users = [ 2025-08-26T19:31:44.7421550Z  requestor 2025-08-26T19:31:44.7422038Z  for requestor in workflow_requestors 2025-08-26T19:31:44.7422710Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.7423325Z  ] 2025-08-26T19:31:44.7423676Z  2025-08-26T19:31:44.7424019Z  enabled = False 2025-08-26T19:31:44.7424479Z  if opted_in_users: 2025-08-26T19:31:44.7425064Z  log.info( 2025-08-26T19:31:44.7425708Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:31:44.7426419Z  ) 2025-08-26T19:31:44.7426821Z  enabled = True 2025-08-26T19:31:44.7427260Z  2025-08-26T19:31:44.7427664Z  elif experiment_settings.rollout_perc: 2025-08-26T19:31:44.7428483Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:31:44.7429411Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:31:44.7430191Z  log.info( 2025-08-26T19:31:44.7431070Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:31:44.7431954Z  ) 2025-08-26T19:31:44.7432386Z  enabled = True 2025-08-26T19:31:44.7432841Z  2025-08-26T19:31:44.7433174Z  if enabled: 2025-08-26T19:31:44.7433626Z  label = experiment_name 2025-08-26T19:31:44.7434194Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:31:44.7435006Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:31:44.7435872Z  # - If it's enabled, then we always list it's prefix first 2025-08-26T19:31:44.7436652Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:31:44.7437300Z  if is_canary: 2025-08-26T19:31:44.7437811Z  label += CANARY_FLEET_SUFFIX 2025-08-26T19:31:44.7438345Z  fleet_prefix = label 2025-08-26T19:31:44.7438833Z  else: 2025-08-26T19:31:44.7439673Z  prefixes.append(label) 2025-08-26T19:31:44.7440266Z  2025-08-26T19:31:44.7440612Z  if len(prefixes) > 1: 2025-08-26T19:31:44.7441069Z  log.error( 2025-08-26T19:31:44.7442097Z  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.7443151Z  ) 2025-08-26T19:31:44.7443546Z  prefixes = prefixes[:1] 2025-08-26T19:31:44.7444013Z  2025-08-26T19:31:44.7444367Z  # Fleet always comes first 2025-08-26T19:31:44.7444850Z  if fleet_prefix: 2025-08-26T19:31:44.7445354Z  prefixes.insert(0, fleet_prefix) 2025-08-26T19:31:44.7445856Z  2025-08-26T19:31:44.7446284Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:31:44.7446845Z  2025-08-26T19:31:44.7447163Z  2025-08-26T19:31:44.7447788Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:31:44.7448530Z  """ 2025-08-26T19:31:44.7449124Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:31:44.7449807Z  2025-08-26T19:31:44.7450465Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:31:44.7451152Z  """ 2025-08-26T19:31:44.7451545Z  gh = get_gh_client(github_token) 2025-08-26T19:31:44.7452100Z  issue = get_issue(gh, repo, issue_num) 2025-08-26T19:31:44.7452736Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:31:44.7453320Z  2025-08-26T19:31:44.7453639Z  2025-08-26T19:31:44.7454229Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:31:44.7455083Z  for _ in range(num_retries): 2025-08-26T19:31:44.7455566Z  try: 2025-08-26T19:31:44.7456027Z  req = Request(url=url, headers=headers) 2025-08-26T19:31:44.7456683Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:31:44.7457323Z  return json.loads(content) 2025-08-26T19:31:44.7457848Z  except Exception as e: 2025-08-26T19:31:44.7458406Z  log.warning(f"Could not download {url}: {e}") 2025-08-26T19:31:44.7458939Z  2025-08-26T19:31:44.7459497Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:31:44.7460299Z  return {} 2025-08-26T19:31:44.7460676Z  2025-08-26T19:31:44.7461005Z  2025-08-26T19:31:44.7461324Z @cache 2025-08-26T19:31:44.7461967Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:31:44.7462706Z  """ 2025-08-26T19:31:44.7463106Z  Dynamically get PR information 2025-08-26T19:31:44.7463593Z  """ 2025-08-26T19:31:44.7464096Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:31:44.7464708Z  headers = { 2025-08-26T19:31:44.7465187Z  "Accept": "application/vnd.github.v3+json", 2025-08-26T19:31:44.7465800Z  "Authorization": f"token {github_token}", 2025-08-26T19:31:44.7466316Z  } 2025-08-26T19:31:44.7466757Z  json_response: dict[str, Any] = download_json( 2025-08-26T19:31:44.7467369Z  url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:31:44.7467896Z  headers=headers, 2025-08-26T19:31:44.7468341Z  ) 2025-08-26T19:31:44.7468681Z  2025-08-26T19:31:44.7469026Z  if not json_response: 2025-08-26T19:31:44.7469615Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:31:44.7470458Z  return {} 2025-08-26T19:31:44.7470860Z  2025-08-26T19:31:44.7471203Z  return json_response 2025-08-26T19:31:44.7471636Z  2025-08-26T19:31:44.7471949Z  2025-08-26T19:31:44.7472533Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:31:44.7473225Z  """ 2025-08-26T19:31:44.7473759Z  Dynamically get the latest list of labels from the pull request 2025-08-26T19:31:44.7474397Z  """ 2025-08-26T19:31:44.7474891Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:31:44.7475486Z  return { 2025-08-26T19:31:44.7476074Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:31:44.7476745Z  } 2025-08-26T19:31:44.7477086Z  2025-08-26T19:31:44.7477425Z  2025-08-26T19:31:44.7477767Z def main() -> None: 2025-08-26T19:31:44.7478234Z  args = parse_args() 2025-08-26T19:31:44.7478664Z  2025-08-26T19:31:44.7479073Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:31:44.7479598Z  2025-08-26T19:31:44.7479956Z  # Check if the PR is opt-out 2025-08-26T19:31:44.7480551Z  if args.pr_number: 2025-08-26T19:31:44.7481225Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:31:44.7481967Z  if OPT_OUT_LABEL in labels: 2025-08-26T19:31:44.7482463Z  log.info( 2025-08-26T19:31:44.7483175Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:31:44.7483921Z  ) 2025-08-26T19:31:44.7484485Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.7485148Z  sys.exit() 2025-08-26T19:31:44.7485673Z  2025-08-26T19:31:44.7486010Z  try: 2025-08-26T19:31:44.7486459Z  rollout_state = get_rollout_state_from_issue( 2025-08-26T19:31:44.7487177Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:31:44.7487807Z  ) 2025-08-26T19:31:44.7488165Z  2025-08-26T19:31:44.7488556Z  username = get_potential_pr_author( 2025-08-26T19:31:44.7489084Z  args.github_token, 2025-08-26T19:31:44.7489580Z  args.github_repo, 2025-08-26T19:31:44.7490159Z  args.github_actor, 2025-08-26T19:31:44.7490661Z  args.github_ref_type, 2025-08-26T19:31:44.7491159Z  args.github_branch, 2025-08-26T19:31:44.7491622Z  ) 2025-08-26T19:31:44.7491985Z  2025-08-26T19:31:44.7492460Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:31:44.7493054Z  2025-08-26T19:31:44.7493458Z  runner_label_prefix = get_runner_prefix( 2025-08-26T19:31:44.7493999Z  rollout_state, 2025-08-26T19:31:44.7494509Z  (args.github_issue_owner, username), 2025-08-26T19:31:44.7495057Z  args.github_branch, 2025-08-26T19:31:44.7495576Z  args.eligible_experiments, 2025-08-26T19:31:44.7496107Z  args.opt_out_experiments, 2025-08-26T19:31:44.7496614Z  is_canary, 2025-08-26T19:31:44.7497038Z  ) 2025-08-26T19:31:44.7497400Z  2025-08-26T19:31:44.7497749Z  except Exception as e: 2025-08-26T19:31:44.7498220Z  log.error( 2025-08-26T19:31:44.7498907Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:31:44.7499758Z  ) 2025-08-26T19:31:44.7500223Z  2025-08-26T19:31:44.7500739Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.7501377Z  2025-08-26T19:31:44.7501701Z  2025-08-26T19:31:44.7502048Z if __name__ == "__main__": 2025-08-26T19:31:44.7502496Z  main() 2025-08-26T19:31:44.7502865Z  2025-08-26T19:31:44.7503181Z EOF 2025-08-26T19:31:44.7503521Z  2025-08-26T19:31:44.7503878Z cat runner_determinator.py 2025-08-26T19:31:44.8042753Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:44.8043510Z env: 2025-08-26T19:31:44.8044125Z GITHUB_TOKEN: *** 2025-08-26T19:31:44.8044513Z ISSUE_NUMBER: 5132 2025-08-26T19:31:44.8044931Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:44.8045408Z ISSUE_OWNER: 2025-08-26T19:31:44.8045783Z CHECK_EXPERIMENTS: 2025-08-26T19:31:44.8046186Z OPT_OUT_EXPERIMENTS: lf 2025-08-26T19:31:44.8046597Z PR_NUMBER: 2025-08-26T19:31:44.8046959Z ##[endgroup] 2025-08-26T19:31:44.8255199Z # flake8: noqa: G004 2025-08-26T19:31:44.8255512Z 2025-08-26T19:31:44.8255956Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:31:44.8256877Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:31:44.8257641Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:31:44.8258060Z 2025-08-26T19:31:44.8258204Z """ 2025-08-26T19:31:44.8258747Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:31:44.8259570Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:31:44.8260665Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:31:44.8261423Z of which runners should be used to run which job. 2025-08-26T19:31:44.8261802Z 2025-08-26T19:31:44.8262179Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:31:44.8263218Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:31:44.8264053Z settings are considered to be empty with only the second part, the user 2025-08-26T19:31:44.8264708Z list, defined. 2025-08-26T19:31:44.8264918Z 2025-08-26T19:31:44.8265269Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:31:44.8266137Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:31:44.8266910Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:31:44.8267335Z 2025-08-26T19:31:44.8267681Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:31:44.8268512Z The user list is also a comma separated list of additional features or 2025-08-26T19:31:44.8269207Z experiments which the user could be opted in to. 2025-08-26T19:31:44.8269592Z 2025-08-26T19:31:44.8269781Z The user list has the following rules: 2025-08-26T19:31:44.8270513Z 2025-08-26T19:31:44.8270827Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:31:44.8271637Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:31:44.8272354Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:31:44.8272718Z 2025-08-26T19:31:44.8272876Z Example config: 2025-08-26T19:31:44.8273296Z # A list of experiments that can be opted into. 2025-08-26T19:31:44.8273911Z # This defines the behavior they'll induce when opted into. 2025-08-26T19:31:44.8274501Z # Expected syntax is: 2025-08-26T19:31:44.8275086Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:31:44.8276021Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:31:44.8276594Z 2025-08-26T19:31:44.8276764Z experiments: 2025-08-26T19:31:44.8277124Z lf: 2025-08-26T19:31:44.8277478Z rollout_percent: 25 2025-08-26T19:31:44.8278046Z all_branches: false 2025-08-26T19:31:44.8278499Z default: true 2025-08-26T19:31:44.8278876Z --- 2025-08-26T19:31:44.8279064Z 2025-08-26T19:31:44.8279217Z # Opt-ins: 2025-08-26T19:31:44.8279789Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:31:44.8280796Z # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:31:44.8281528Z # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:31:44.8282136Z # Experiments should be from the above list. 2025-08-26T19:31:44.8282489Z 2025-08-26T19:31:44.8282660Z @User1,-lf,split_build 2025-08-26T19:31:44.8283058Z @User2,lf 2025-08-26T19:31:44.8283407Z @User3,split_build 2025-08-26T19:31:44.8283776Z """ 2025-08-26T19:31:44.8283953Z 2025-08-26T19:31:44.8284104Z import json 2025-08-26T19:31:44.8284436Z import logging 2025-08-26T19:31:44.8284791Z import os 2025-08-26T19:31:44.8285119Z import random 2025-08-26T19:31:44.8285464Z import re 2025-08-26T19:31:44.8285796Z import sys 2025-08-26T19:31:44.8286161Z from argparse import ArgumentParser 2025-08-26T19:31:44.8286659Z from collections.abc import Iterable 2025-08-26T19:31:44.8287136Z from functools import cache 2025-08-26T19:31:44.8287573Z from logging import LogRecord 2025-08-26T19:31:44.8288020Z from typing import Any, NamedTuple 2025-08-26T19:31:44.8288511Z from urllib.request import Request, urlopen 2025-08-26T19:31:44.8288871Z 2025-08-26T19:31:44.8289024Z import yaml 2025-08-26T19:31:44.8289378Z from github import Auth, Github 2025-08-26T19:31:44.8289822Z from github.Issue import Issue 2025-08-26T19:31:44.8290282Z 2025-08-26T19:31:44.8290289Z 2025-08-26T19:31:44.8290497Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:31:44.8291146Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:31:44.8291958Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:31:44.8292487Z 2025-08-26T19:31:44.8292715Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:31:44.8293364Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:31:44.8293847Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:31:44.8294357Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:31:44.8294690Z 2025-08-26T19:31:44.8294870Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:31:44.8295174Z 2025-08-26T19:31:44.8295352Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:31:44.8295773Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:31:44.8296040Z 2025-08-26T19:31:44.8296047Z 2025-08-26T19:31:44.8296223Z class Experiment(NamedTuple): 2025-08-26T19:31:44.8296663Z rollout_perc: float = ( 2025-08-26T19:31:44.8297255Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:31:44.8297879Z ) 2025-08-26T19:31:44.8298220Z all_branches: bool = ( 2025-08-26T19:31:44.8298807Z False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:31:44.8299427Z ) 2025-08-26T19:31:44.8299758Z default: bool = ( 2025-08-26T19:31:44.8300656Z True # If True, the experiment is enabled by default for all queries 2025-08-26T19:31:44.8301269Z ) 2025-08-26T19:31:44.8301445Z 2025-08-26T19:31:44.8301614Z # Add more fields as needed 2025-08-26T19:31:44.8301895Z 2025-08-26T19:31:44.8301901Z 2025-08-26T19:31:44.8302071Z class Settings(NamedTuple): 2025-08-26T19:31:44.8302474Z """ 2025-08-26T19:31:44.8302883Z Settings for the experiments that can be opted into. 2025-08-26T19:31:44.8303425Z """ 2025-08-26T19:31:44.8303598Z 2025-08-26T19:31:44.8303790Z experiments: dict[str, Experiment] = {} 2025-08-26T19:31:44.8304135Z 2025-08-26T19:31:44.8304142Z 2025-08-26T19:31:44.8304338Z class ColorFormatter(logging.Formatter): 2025-08-26T19:31:44.8304917Z """Color codes the log messages based on the log level""" 2025-08-26T19:31:44.8305328Z 2025-08-26T19:31:44.8305478Z COLORS = { 2025-08-26T19:31:44.8305847Z "WARNING": "\033[33m", # Yellow 2025-08-26T19:31:44.8306452Z "ERROR": "\033[31m", # Red 2025-08-26T19:31:44.8306921Z "CRITICAL": "\033[31m", # Red 2025-08-26T19:31:44.8307380Z "INFO": "\033[0m", # Reset 2025-08-26T19:31:44.8307832Z "DEBUG": "\033[0m", # Reset 2025-08-26T19:31:44.8308261Z } 2025-08-26T19:31:44.8308443Z 2025-08-26T19:31:44.8308644Z def format(self, record: LogRecord) -> str: 2025-08-26T19:31:44.8309331Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:31:44.8310279Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:31:44.8310825Z return super().format(record) 2025-08-26T19:31:44.8311130Z 2025-08-26T19:31:44.8311138Z 2025-08-26T19:31:44.8311317Z handler = logging.StreamHandler() 2025-08-26T19:31:44.8311981Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:31:44.8312491Z 2025-08-26T19:31:44.8312714Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:31:44.8313262Z log.addHandler(handler) 2025-08-26T19:31:44.8313701Z log.setLevel(logging.INFO) 2025-08-26T19:31:44.8313964Z 2025-08-26T19:31:44.8313970Z 2025-08-26T19:31:44.8314201Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:31:44.8314721Z """ 2025-08-26T19:31:44.8315184Z Defines outputs of the github action that invokes this script 2025-08-26T19:31:44.8315763Z """ 2025-08-26T19:31:44.8316087Z if not GITHUB_OUTPUT: 2025-08-26T19:31:44.8317104Z # 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.8318152Z log.warning( 2025-08-26T19:31:44.8318944Z "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.8319815Z ) 2025-08-26T19:31:44.8329611Z print(f"::set-output name={key}::{value}") 2025-08-26T19:31:44.8330446Z return 2025-08-26T19:31:44.8330661Z 2025-08-26T19:31:44.8331031Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:31:44.8331604Z log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:31:44.8332151Z f.write(f"{key}={value}\n") 2025-08-26T19:31:44.8332465Z 2025-08-26T19:31:44.8332473Z 2025-08-26T19:31:44.8332771Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:31:44.8333380Z return frozenset( 2025-08-26T19:31:44.8333952Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:31:44.8334600Z ) 2025-08-26T19:31:44.8334775Z 2025-08-26T19:31:44.8334781Z 2025-08-26T19:31:44.8334952Z def parse_args() -> Any: 2025-08-26T19:31:44.8335472Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:31:44.8336283Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:31:44.8337009Z parser.add_argument( 2025-08-26T19:31:44.8337442Z "--github-issue-repo", 2025-08-26T19:31:44.8337864Z type=str, 2025-08-26T19:31:44.8338238Z required=False, 2025-08-26T19:31:44.8338652Z default="pytorch/test-infra", 2025-08-26T19:31:44.8339152Z help="GitHub repo to get the issue", 2025-08-26T19:31:44.8339615Z ) 2025-08-26T19:31:44.8339949Z parser.add_argument( 2025-08-26T19:31:44.8340569Z "--github-repo", 2025-08-26T19:31:44.8340965Z type=str, 2025-08-26T19:31:44.8341345Z required=True, 2025-08-26T19:31:44.8341767Z help="GitHub repo where CI is running", 2025-08-26T19:31:44.8342255Z ) 2025-08-26T19:31:44.8342584Z parser.add_argument( 2025-08-26T19:31:44.8343144Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:31:44.8343754Z ) 2025-08-26T19:31:44.8344084Z parser.add_argument( 2025-08-26T19:31:44.8344663Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:31:44.8395230Z ) 2025-08-26T19:31:44.8395991Z parser.add_argument( 2025-08-26T19:31:44.8397264Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:31:44.8397973Z ) 2025-08-26T19:31:44.8398313Z parser.add_argument( 2025-08-26T19:31:44.8398950Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:31:44.8399614Z ) 2025-08-26T19:31:44.8399963Z parser.add_argument( 2025-08-26T19:31:44.8400635Z "--github-ref-type", 2025-08-26T19:31:44.8401077Z type=str, 2025-08-26T19:31:44.8401441Z required=True, 2025-08-26T19:31:44.8401892Z help="Current GitHub ref type, branch or tag", 2025-08-26T19:31:44.8402402Z ) 2025-08-26T19:31:44.8402740Z parser.add_argument( 2025-08-26T19:31:44.8403164Z "--eligible-experiments", 2025-08-26T19:31:44.8403643Z type=_str_comma_separated_to_set, 2025-08-26T19:31:44.8404127Z required=False, 2025-08-26T19:31:44.8404509Z default="", 2025-08-26T19:31:44.8405319Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:31:44.8406199Z ) 2025-08-26T19:31:44.8406535Z parser.add_argument( 2025-08-26T19:31:44.8406951Z "--opt-out-experiments", 2025-08-26T19:31:44.8407424Z type=_str_comma_separated_to_set, 2025-08-26T19:31:44.8407906Z required=False, 2025-08-26T19:31:44.8408290Z default="", 2025-08-26T19:31:44.8408645Z help=( 2025-08-26T19:31:44.8409270Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:31:44.8410545Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:31:44.8411331Z ), 2025-08-26T19:31:44.8411658Z ) 2025-08-26T19:31:44.8411986Z parser.add_argument( 2025-08-26T19:31:44.8412389Z "--pr-number", 2025-08-26T19:31:44.8412761Z type=str, 2025-08-26T19:31:44.8413126Z required=False, 2025-08-26T19:31:44.8413521Z default="", 2025-08-26T19:31:44.8414159Z help="the optional PR number where this is run", 2025-08-26T19:31:44.8414695Z ) 2025-08-26T19:31:44.8414879Z 2025-08-26T19:31:44.8415059Z return parser.parse_args() 2025-08-26T19:31:44.8415353Z 2025-08-26T19:31:44.8415360Z 2025-08-26T19:31:44.8415740Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.8416443Z auth = Auth.Token(github_token) 2025-08-26T19:31:44.8416916Z return Github(auth=auth) 2025-08-26T19:31:44.8417190Z 2025-08-26T19:31:44.8417196Z 2025-08-26T19:31:44.8417624Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:31:44.8418362Z repo = gh.get_repo(repo) 2025-08-26T19:31:44.8418821Z return repo.get_issue(number=issue_num) 2025-08-26T19:31:44.8419157Z 2025-08-26T19:31:44.8419163Z 2025-08-26T19:31:44.8419331Z def get_potential_pr_author( 2025-08-26T19:31:44.8419944Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:31:44.8420759Z ) -> str: 2025-08-26T19:31:44.8421252Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:31:44.8422010Z # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:31:44.8422694Z # embedded in the tag name: ciflow// 2025-08-26T19:31:44.8423088Z 2025-08-26T19:31:44.8423266Z gh = get_gh_client(github_token) 2025-08-26T19:31:44.8423579Z 2025-08-26T19:31:44.8423831Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:31:44.8424432Z split_tag = ref_name.split("/") 2025-08-26T19:31:44.8424912Z if ( 2025-08-26T19:31:44.8425268Z len(split_tag) == 3 2025-08-26T19:31:44.8425726Z and split_tag[0] == "ciflow" 2025-08-26T19:31:44.8426210Z and split_tag[2].isnumeric() 2025-08-26T19:31:44.8426677Z ): 2025-08-26T19:31:44.8427024Z pr_number = split_tag[2] 2025-08-26T19:31:44.8427607Z try: 2025-08-26T19:31:44.8427994Z repository = gh.get_repo(repo) 2025-08-26T19:31:44.8428577Z pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:31:44.8429134Z except Exception as e: 2025-08-26T19:31:44.8429622Z raise Exception( # noqa: TRY002 2025-08-26T19:31:44.8430430Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:31:44.8431028Z ) from e 2025-08-26T19:31:44.8431524Z return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:31:44.8432174Z # In all other cases, return the original input username 2025-08-26T19:31:44.8432717Z return username 2025-08-26T19:31:44.8432936Z 2025-08-26T19:31:44.8432943Z 2025-08-26T19:31:44.8433152Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:31:44.8433650Z """ 2025-08-26T19:31:44.8434251Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:31:44.8434992Z """ 2025-08-26T19:31:44.8435502Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:31:44.8435988Z 2025-08-26T19:31:44.8435995Z 2025-08-26T19:31:44.8436172Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:31:44.8436627Z try: 2025-08-26T19:31:44.8436979Z data = yaml.safe_load(yaml_text) 2025-08-26T19:31:44.8437454Z return data 2025-08-26T19:31:44.8437826Z except yaml.YAMLError: 2025-08-26T19:31:44.8438271Z log.exception("Error loading YAML") 2025-08-26T19:31:44.8438746Z raise 2025-08-26T19:31:44.8438940Z 2025-08-26T19:31:44.8438946Z 2025-08-26T19:31:44.8439337Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:31:44.8440232Z """ 2025-08-26T19:31:44.8440817Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:31:44.8441394Z 2025-08-26T19:31:44.8441888Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.8442617Z and the text below is the list of opted in users. 2025-08-26T19:31:44.8443002Z 2025-08-26T19:31:44.8443357Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:31:44.8444018Z """ 2025-08-26T19:31:44.8444424Z rollout_state_parts = rollout_state.split("---") 2025-08-26T19:31:44.8444975Z if len(rollout_state_parts) >= 2: 2025-08-26T19:31:44.8445528Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:31:44.8446075Z else: 2025-08-26T19:31:44.8446423Z return "", rollout_state 2025-08-26T19:31:44.8446718Z 2025-08-26T19:31:44.8446725Z 2025-08-26T19:31:44.8446911Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:31:44.8447411Z """ 2025-08-26T19:31:44.8447887Z Dictionary of users with a list of features they have opted into 2025-08-26T19:31:44.8448493Z """ 2025-08-26T19:31:44.8448677Z 2025-08-26T19:31:44.8448683Z 2025-08-26T19:31:44.8449011Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:31:44.8449616Z """ 2025-08-26T19:31:44.8450476Z 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.8451160Z 2025-08-26T19:31:44.8451752Z 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.8452690Z - Example line: "@User1,lf,split_build" 2025-08-26T19:31:44.8453326Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:31:44.8453784Z 2025-08-26T19:31:44.8453791Z 2025-08-26T19:31:44.8453938Z """ 2025-08-26T19:31:44.8454267Z optins = UserOptins() 2025-08-26T19:31:44.8454716Z for user in user_optin_text.split("\n"): 2025-08-26T19:31:44.8455228Z user = user.strip("\r\n\t -") 2025-08-26T19:31:44.8455739Z if not user or not user.startswith("@"): 2025-08-26T19:31:44.8456401Z # Not a valid user. Skip 2025-08-26T19:31:44.8456849Z continue 2025-08-26T19:31:44.8457074Z 2025-08-26T19:31:44.8457223Z if user: 2025-08-26T19:31:44.8457618Z usr_name = user.split(",")[0].strip("@") 2025-08-26T19:31:44.8458268Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:31:44.8458724Z 2025-08-26T19:31:44.8458875Z return optins 2025-08-26T19:31:44.8459098Z 2025-08-26T19:31:44.8459105Z 2025-08-26T19:31:44.8459370Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:31:44.8459936Z """ 2025-08-26T19:31:44.8460483Z Check if the experiment name is valid. 2025-08-26T19:31:44.8460970Z A valid name: 2025-08-26T19:31:44.8461547Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:31:44.8462430Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:31:44.8463108Z - Cannot contain spaces 2025-08-26T19:31:44.8463531Z """ 2025-08-26T19:31:44.8463707Z 2025-08-26T19:31:44.8463955Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:31:44.8464628Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:31:44.8465043Z 2025-08-26T19:31:44.8465194Z if valid: 2025-08-26T19:31:44.8465533Z return True 2025-08-26T19:31:44.8465752Z 2025-08-26T19:31:44.8465903Z log.error( 2025-08-26T19:31:44.8467268Z 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.8468731Z ) 2025-08-26T19:31:44.8469053Z return False 2025-08-26T19:31:44.8469263Z 2025-08-26T19:31:44.8469270Z 2025-08-26T19:31:44.8469549Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:31:44.8470295Z """ 2025-08-26T19:31:44.8471024Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:31:44.8471701Z """ 2025-08-26T19:31:44.8472010Z try: 2025-08-26T19:31:44.8472349Z if settings_text: 2025-08-26T19:31:44.8473025Z # 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.8473774Z # for easy reading 2025-08-26T19:31:44.8474515Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:31:44.8475360Z # the backtick character in shell commands. 2025-08-26T19:31:44.8475928Z backtick = chr(96) # backtick character 2025-08-26T19:31:44.8476544Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:31:44.8477163Z settings = load_yaml(settings_text) 2025-08-26T19:31:44.8477506Z 2025-08-26T19:31:44.8477908Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:31:44.8478605Z experiments = {} 2025-08-26T19:31:44.8478884Z 2025-08-26T19:31:44.8479246Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:31:44.8479942Z if not is_valid_experiment_name(exp_name): 2025-08-26T19:31:44.8481208Z # 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.8482185Z continue 2025-08-26T19:31:44.8482446Z 2025-08-26T19:31:44.8482615Z valid_settings = {} 2025-08-26T19:31:44.8483093Z for setting in exp_settings: 2025-08-26T19:31:44.8483616Z if setting not in Experiment._fields: 2025-08-26T19:31:44.8484137Z log.warning( 2025-08-26T19:31:44.8484783Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:31:44.8485599Z ) 2025-08-26T19:31:44.8485984Z else: 2025-08-26T19:31:44.8486455Z valid_settings[setting] = exp_settings[setting] 2025-08-26T19:31:44.8486846Z 2025-08-26T19:31:44.8487111Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:31:44.8487739Z return Settings(experiments) 2025-08-26T19:31:44.8488071Z 2025-08-26T19:31:44.8488229Z except Exception: 2025-08-26T19:31:44.8488665Z log.exception("Failed to parse settings") 2025-08-26T19:31:44.8489031Z 2025-08-26T19:31:44.8489185Z return Settings() 2025-08-26T19:31:44.8489418Z 2025-08-26T19:31:44.8489425Z 2025-08-26T19:31:44.8489659Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:31:44.8490391Z """ 2025-08-26T19:31:44.8490792Z Parse settings, if any, from the rollout state. 2025-08-26T19:31:44.8491165Z 2025-08-26T19:31:44.8491490Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:44.8492206Z and the text below is the list of opted in users. 2025-08-26T19:31:44.8492589Z 2025-08-26T19:31:44.8492977Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:31:44.8493664Z """ 2025-08-26T19:31:44.8494440Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.8495277Z return parse_settings_from_text(settings_text) 2025-08-26T19:31:44.8495652Z 2025-08-26T19:31:44.8495668Z 2025-08-26T19:31:44.8495898Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:31:44.8496421Z """ 2025-08-26T19:31:44.8496866Z Parse users from the rollout state. 2025-08-26T19:31:44.8497301Z 2025-08-26T19:31:44.8497447Z """ 2025-08-26T19:31:44.8497937Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:44.8498616Z return parse_user_opt_in_from_text(users_text) 2025-08-26T19:31:44.8499003Z 2025-08-26T19:31:44.8499010Z 2025-08-26T19:31:44.8499543Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.8500510Z """ 2025-08-26T19:31:44.8500885Z Check if a user is opted into an experiment 2025-08-26T19:31:44.8501375Z """ 2025-08-26T19:31:44.8501786Z return experiment_name in user_optins.get(user, []) 2025-08-26T19:31:44.8502176Z 2025-08-26T19:31:44.8502182Z 2025-08-26T19:31:44.8502574Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:44.8503272Z """ 2025-08-26T19:31:44.8503680Z Check if a user explicitly opted out of an experiment 2025-08-26T19:31:44.8504214Z """ 2025-08-26T19:31:44.8504670Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:31:44.8505308Z experiment_optout = "-" + experiment_name 2025-08-26T19:31:44.8505894Z if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:31:44.8506452Z return False 2025-08-26T19:31:44.8506680Z 2025-08-26T19:31:44.8506941Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:31:44.8507493Z log.warning( 2025-08-26T19:31:44.8508241Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:31:44.8509058Z ) 2025-08-26T19:31:44.8509246Z 2025-08-26T19:31:44.8509390Z return True 2025-08-26T19:31:44.8509598Z 2025-08-26T19:31:44.8509604Z 2025-08-26T19:31:44.8509764Z def get_runner_prefix( 2025-08-26T19:31:44.8510726Z rollout_state: str, 2025-08-26T19:31:44.8511156Z workflow_requestors: Iterable[str], 2025-08-26T19:31:44.8511630Z branch: str, 2025-08-26T19:31:44.8512079Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.8512692Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:44.8513229Z is_canary: bool = False, 2025-08-26T19:31:44.8513665Z ) -> str: 2025-08-26T19:31:44.8514202Z settings = parse_settings(rollout_state) 2025-08-26T19:31:44.8514735Z user_optins = parse_users(rollout_state) 2025-08-26T19:31:44.8515084Z 2025-08-26T19:31:44.8515237Z fleet_prefix = "" 2025-08-26T19:31:44.8515615Z prefixes = [] 2025-08-26T19:31:44.8516181Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:31:44.8517061Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:31:44.8517718Z log.info( 2025-08-26T19:31:44.8518348Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:31:44.8519041Z ) 2025-08-26T19:31:44.8519380Z continue 2025-08-26T19:31:44.8519602Z 2025-08-26T19:31:44.8519773Z if opt_out_experiments: 2025-08-26T19:31:44.8520374Z if experiment_name in opt_out_experiments: 2025-08-26T19:31:44.8520963Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:31:44.8521501Z log.info( 2025-08-26T19:31:44.8522362Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:31:44.8523267Z ) 2025-08-26T19:31:44.8523622Z continue 2025-08-26T19:31:44.8523862Z 2025-08-26T19:31:44.8524033Z if eligible_experiments: 2025-08-26T19:31:44.8524558Z if experiment_name not in eligible_experiments: 2025-08-26T19:31:44.8525136Z exp_list = ", ".join(eligible_experiments) 2025-08-26T19:31:44.8525642Z log.info( 2025-08-26T19:31:44.8526374Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:31:44.8527156Z ) 2025-08-26T19:31:44.8527511Z continue 2025-08-26T19:31:44.8527936Z elif not experiment_settings.default: 2025-08-26T19:31:44.8528426Z log.info( 2025-08-26T19:31:44.8529149Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:31:44.8529832Z ) 2025-08-26T19:31:44.8530270Z continue 2025-08-26T19:31:44.8530495Z 2025-08-26T19:31:44.8530750Z # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:31:44.8531315Z opted_out_users = [ 2025-08-26T19:31:44.8531720Z requestor 2025-08-26T19:31:44.8532126Z for requestor in workflow_requestors 2025-08-26T19:31:44.8532740Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.8533320Z ] 2025-08-26T19:31:44.8533504Z 2025-08-26T19:31:44.8533673Z if opted_out_users: 2025-08-26T19:31:44.8534077Z log.info( 2025-08-26T19:31:44.8534639Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:31:44.8535276Z ) 2025-08-26T19:31:44.8535610Z continue 2025-08-26T19:31:44.8535833Z 2025-08-26T19:31:44.8536082Z # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:31:44.8536648Z opted_in_users = [ 2025-08-26T19:31:44.8537043Z requestor 2025-08-26T19:31:44.8537444Z for requestor in workflow_requestors 2025-08-26T19:31:44.8538055Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:31:44.8538612Z ] 2025-08-26T19:31:44.8538789Z 2025-08-26T19:31:44.8538943Z enabled = False 2025-08-26T19:31:44.8539332Z if opted_in_users: 2025-08-26T19:31:44.8539722Z log.info( 2025-08-26T19:31:44.8540357Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:31:44.8540991Z ) 2025-08-26T19:31:44.8541329Z enabled = True 2025-08-26T19:31:44.8541585Z 2025-08-26T19:31:44.8541783Z elif experiment_settings.rollout_perc: 2025-08-26T19:31:44.8542557Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:31:44.8543558Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:31:44.8544159Z log.info( 2025-08-26T19:31:44.8544959Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:31:44.8545815Z ) 2025-08-26T19:31:44.8546179Z enabled = True 2025-08-26T19:31:44.8546452Z 2025-08-26T19:31:44.8546597Z if enabled: 2025-08-26T19:31:44.8546971Z label = experiment_name 2025-08-26T19:31:44.8547468Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:31:44.8548237Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:31:44.8549042Z # - If it's enabled, then we always list it's prefix first 2025-08-26T19:31:44.8549742Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:31:44.8550476Z if is_canary: 2025-08-26T19:31:44.8550921Z label += CANARY_FLEET_SUFFIX 2025-08-26T19:31:44.8551429Z fleet_prefix = label 2025-08-26T19:31:44.8551874Z else: 2025-08-26T19:31:44.8552253Z prefixes.append(label) 2025-08-26T19:31:44.8552558Z 2025-08-26T19:31:44.8552719Z if len(prefixes) > 1: 2025-08-26T19:31:44.8553114Z log.error( 2025-08-26T19:31:44.8554064Z 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.8555104Z ) 2025-08-26T19:31:44.8555449Z prefixes = prefixes[:1] 2025-08-26T19:31:44.8555733Z 2025-08-26T19:31:44.8555902Z # Fleet always comes first 2025-08-26T19:31:44.8556322Z if fleet_prefix: 2025-08-26T19:31:44.8556725Z prefixes.insert(0, fleet_prefix) 2025-08-26T19:31:44.8557060Z 2025-08-26T19:31:44.8557403Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:31:44.8557799Z 2025-08-26T19:31:44.8557805Z 2025-08-26T19:31:44.8558218Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:31:44.8558940Z """ 2025-08-26T19:31:44.8559475Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:31:44.8560092Z 2025-08-26T19:31:44.8560457Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:31:44.8561096Z """ 2025-08-26T19:31:44.8561434Z gh = get_gh_client(github_token) 2025-08-26T19:31:44.8561927Z issue = get_issue(gh, repo, issue_num) 2025-08-26T19:31:44.8562507Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:31:44.8562925Z 2025-08-26T19:31:44.8562931Z 2025-08-26T19:31:44.8563296Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:31:44.8563993Z for _ in range(num_retries): 2025-08-26T19:31:44.8564426Z try: 2025-08-26T19:31:44.8564806Z req = Request(url=url, headers=headers) 2025-08-26T19:31:44.8565417Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:31:44.8566010Z return json.loads(content) 2025-08-26T19:31:44.8566485Z except Exception as e: 2025-08-26T19:31:44.8566973Z log.warning(f"Could not download {url}: {e}") 2025-08-26T19:31:44.8567367Z 2025-08-26T19:31:44.8567727Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:31:44.8568382Z return {} 2025-08-26T19:31:44.8568608Z 2025-08-26T19:31:44.8568615Z 2025-08-26T19:31:44.8568769Z @cache 2025-08-26T19:31:44.8569341Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:31:44.8570144Z """ 2025-08-26T19:31:44.8570493Z Dynamically get PR information 2025-08-26T19:31:44.8570941Z """ 2025-08-26T19:31:44.8571520Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:31:44.8572097Z headers = { 2025-08-26T19:31:44.8572509Z "Accept": "application/vnd.github.v3+json", 2025-08-26T19:31:44.8573062Z "Authorization": f"token {github_token}", 2025-08-26T19:31:44.8573545Z } 2025-08-26T19:31:44.8573921Z json_response: dict[str, Any] = download_json( 2025-08-26T19:31:44.8574472Z url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:31:44.8574973Z headers=headers, 2025-08-26T19:31:44.8575361Z ) 2025-08-26T19:31:44.8575532Z 2025-08-26T19:31:44.8575692Z if not json_response: 2025-08-26T19:31:44.8576215Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:31:44.8576777Z return {} 2025-08-26T19:31:44.8576986Z 2025-08-26T19:31:44.8577144Z return json_response 2025-08-26T19:31:44.8577389Z 2025-08-26T19:31:44.8577396Z 2025-08-26T19:31:44.8577772Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:31:44.8578480Z """ 2025-08-26T19:31:44.8578966Z Dynamically get the latest list of labels from the pull request 2025-08-26T19:31:44.8579559Z """ 2025-08-26T19:31:44.8580090Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:31:44.8580653Z return { 2025-08-26T19:31:44.8581178Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:31:44.8581816Z } 2025-08-26T19:31:44.8581989Z 2025-08-26T19:31:44.8581996Z 2025-08-26T19:31:44.8582146Z def main() -> None: 2025-08-26T19:31:44.8582517Z args = parse_args() 2025-08-26T19:31:44.8582759Z 2025-08-26T19:31:44.8582956Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:31:44.8583308Z 2025-08-26T19:31:44.8583483Z # Check if the PR is opt-out 2025-08-26T19:31:44.8583923Z if args.pr_number: 2025-08-26T19:31:44.8584524Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:31:44.8585349Z if OPT_OUT_LABEL in labels: 2025-08-26T19:31:44.8585802Z log.info( 2025-08-26T19:31:44.8586445Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:31:44.8587172Z ) 2025-08-26T19:31:44.8587669Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.8588280Z sys.exit() 2025-08-26T19:31:44.8588515Z 2025-08-26T19:31:44.8588654Z try: 2025-08-26T19:31:44.8589042Z rollout_state = get_rollout_state_from_issue( 2025-08-26T19:31:44.8589709Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:31:44.8590402Z ) 2025-08-26T19:31:44.8590584Z 2025-08-26T19:31:44.8590770Z username = get_potential_pr_author( 2025-08-26T19:31:44.8591260Z args.github_token, 2025-08-26T19:31:44.8591685Z args.github_repo, 2025-08-26T19:31:44.8592120Z args.github_actor, 2025-08-26T19:31:44.8592565Z args.github_ref_type, 2025-08-26T19:31:44.8593015Z args.github_branch, 2025-08-26T19:31:44.8593433Z ) 2025-08-26T19:31:44.8593616Z 2025-08-26T19:31:44.8593871Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:31:44.8594280Z 2025-08-26T19:31:44.8594481Z runner_label_prefix = get_runner_prefix( 2025-08-26T19:31:44.8594979Z rollout_state, 2025-08-26T19:31:44.8595418Z (args.github_issue_owner, username), 2025-08-26T19:31:44.8595911Z args.github_branch, 2025-08-26T19:31:44.8596367Z args.eligible_experiments, 2025-08-26T19:31:44.8596851Z args.opt_out_experiments, 2025-08-26T19:31:44.8597309Z is_canary, 2025-08-26T19:31:44.8597680Z ) 2025-08-26T19:31:44.8597861Z 2025-08-26T19:31:44.8598025Z except Exception as e: 2025-08-26T19:31:44.8598434Z log.error( 2025-08-26T19:31:44.8599040Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:31:44.8599876Z ) 2025-08-26T19:31:44.8600163Z 2025-08-26T19:31:44.8600467Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:44.8600930Z 2025-08-26T19:31:44.8600937Z 2025-08-26T19:31:44.8601093Z if __name__ == "__main__": 2025-08-26T19:31:44.8601489Z main() 2025-08-26T19:31:44.8601672Z 2025-08-26T19:31:44.8687743Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:31:44.8688555Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:31:44.8728190Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:44.8728632Z env: 2025-08-26T19:31:44.8729180Z GITHUB_TOKEN: *** 2025-08-26T19:31:44.8729556Z ISSUE_NUMBER: 5132 2025-08-26T19:31:44.8729957Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:44.8730731Z ISSUE_OWNER: 2025-08-26T19:31:44.8731089Z CHECK_EXPERIMENTS: 2025-08-26T19:31:44.8731473Z OPT_OUT_EXPERIMENTS: lf 2025-08-26T19:31:44.8731885Z PR_NUMBER: 2025-08-26T19:31:44.8732223Z ##[endgroup] 2025-08-26T19:31:46.3096838Z Defaulting to user installation because normal site-packages is not writeable 2025-08-26T19:31:47.4249280Z Collecting urllib3==1.26.18 2025-08-26T19:31:47.4558970Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-26T19:31:47.4767604Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.1 MB/s eta 0:00:00 2025-08-26T19:31:47.5026919Z Collecting PyGithub==2.3.0 2025-08-26T19:31:47.5050828Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-26T19:31:47.5451831Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-26T19:31:47.5474849Z 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:47.5519955Z 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:47.5536194Z 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:47.5550551Z 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:47.5795261Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-26T19:31:47.5818766Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-26T19:31:47.6113040Z 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:47.7181326Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:31:47.7205631Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-26T19:31:47.8354852Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-26T19:31:47.8381097Z 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:47.8552508Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:31:47.8600593Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-26T19:31:47.8811044Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-26T19:31:47.8871181Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 31.7 MB/s eta 0:00:00 2025-08-26T19:31:47.8894351Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-26T19:31:47.8951661Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 86.1 MB/s eta 0:00:00 2025-08-26T19:31:47.8973662Z 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.9057552Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 127.9 MB/s eta 0:00:00 2025-08-26T19:31:47.9080423Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-26T19:31:47.9120558Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-26T19:31:47.9181871Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 106.4 MB/s eta 0:00:00 2025-08-26T19:31:47.9255667Z 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.9297183Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 29.9 MB/s eta 0:00:00 2025-08-26T19:31:47.9319794Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-26T19:31:47.9365731Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 35.6 MB/s eta 0:00:00 2025-08-26T19:31:48.2401615Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-26T19:31:48.7717571Z 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.8461761Z ##[group]Run curr_branch="main" 2025-08-26T19:31:48.8462070Z curr_branch="main" 2025-08-26T19:31:48.8462290Z curr_ref_type="branch" 2025-08-26T19:31:48.8462569Z echo "Current branch is '$curr_branch'" 2025-08-26T19:31:48.8462832Z  2025-08-26T19:31:48.8463022Z python3 runner_determinator.py \ 2025-08-26T19:31:48.8463317Z  --github-token "$GITHUB_TOKEN" \ 2025-08-26T19:31:48.8463584Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-26T19:31:48.8463841Z  --github-branch "$curr_branch" \ 2025-08-26T19:31:48.8464102Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-26T19:31:48.8464373Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-26T19:31:48.8464646Z  --github-ref-type "$curr_ref_type" \ 2025-08-26T19:31:48.8464902Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-26T19:31:48.8465204Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-26T19:31:48.8465560Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-26T19:31:48.8465842Z  --pr-number "${PR_NUMBER}" 2025-08-26T19:31:48.8506659Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:48.8506884Z env: 2025-08-26T19:31:48.8507448Z GITHUB_TOKEN: *** 2025-08-26T19:31:48.8507638Z ISSUE_NUMBER: 5132 2025-08-26T19:31:48.8507838Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:48.8508065Z ISSUE_OWNER: 2025-08-26T19:31:48.8508246Z CHECK_EXPERIMENTS: 2025-08-26T19:31:48.8508442Z OPT_OUT_EXPERIMENTS: lf 2025-08-26T19:31:48.8508639Z PR_NUMBER: 2025-08-26T19:31:48.8508797Z ##[endgroup] 2025-08-26T19:31:48.8566586Z Current branch is 'main' 2025-08-26T19:31:50.6717159Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-08-26T19:31:50.6718208Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-26T19:31:50.6718933Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-26T19:31:50.6719556Z INFO : Setting output: label-type='' 2025-08-26T19:31:50.7025202Z Evaluate and set job outputs 2025-08-26T19:31:50.7032558Z Cleaning up orphan processes