2025-11-03T15:43:06.1223154Z Current runner version: '2.329.0' 2025-11-03T15:43:06.1257507Z ##[group]Runner Image Provisioner 2025-11-03T15:43:06.1258634Z Hosted Compute Agent 2025-11-03T15:43:06.1259596Z Version: 20251016.436 2025-11-03T15:43:06.1260868Z Commit: 8ab8ac8bfd662a3739dab9fe09456aba92132568 2025-11-03T15:43:06.1262076Z Build Date: 2025-10-15T20:44:12Z 2025-11-03T15:43:06.1263811Z ##[endgroup] 2025-11-03T15:43:06.1264685Z ##[group]Operating System 2025-11-03T15:43:06.1265641Z Ubuntu 2025-11-03T15:43:06.1266579Z 24.04.3 2025-11-03T15:43:06.1267399Z LTS 2025-11-03T15:43:06.1268119Z ##[endgroup] 2025-11-03T15:43:06.1269171Z ##[group]Runner Image 2025-11-03T15:43:06.1270168Z Image: ubuntu-24.04 2025-11-03T15:43:06.1271255Z Version: 20251030.96.2 2025-11-03T15:43:06.1273237Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251030.96/images/ubuntu/Ubuntu2404-Readme.md 2025-11-03T15:43:06.1275874Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251030.96 2025-11-03T15:43:06.1277672Z ##[endgroup] 2025-11-03T15:43:06.1279342Z ##[group]GITHUB_TOKEN Permissions 2025-11-03T15:43:06.1282521Z Contents: read 2025-11-03T15:43:06.1283420Z Metadata: read 2025-11-03T15:43:06.1284318Z ##[endgroup] 2025-11-03T15:43:06.1287675Z Secret source: Actions 2025-11-03T15:43:06.1289201Z Prepare workflow directory 2025-11-03T15:43:06.2046849Z Prepare all required actions 2025-11-03T15:43:06.2132046Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (3f6538febd727b782e6e13cfd026a309fb14351d) 2025-11-03T15:43:06.2140081Z ##[group] Inputs 2025-11-03T15:43:06.2141167Z check_experiments: 2025-11-03T15:43:06.2142084Z opt_out_experiments: lf 2025-11-03T15:43:06.2143180Z triggering_actor: pytorchmergebot 2025-11-03T15:43:06.2144256Z issue_owner: 2025-11-03T15:43:06.2145065Z curr_branch: main 2025-11-03T15:43:06.2146180Z curr_ref_type: branch 2025-11-03T15:43:06.2147282Z issue_number: 5132 2025-11-03T15:43:06.2148185Z ##[endgroup] 2025-11-03T15:43:06.2149436Z Complete job name: unit-test / get-label-type / runner-determinator 2025-11-03T15:43:06.7070613Z ##[group]Run cat < runner_determinator.py 2025-11-03T15:43:06.7074003Z cat < runner_determinator.py 2025-11-03T15:43:06.7074719Z # flake8: noqa: G004 2025-11-03T15:43:06.7075297Z  2025-11-03T15:43:06.7076292Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-11-03T15:43:06.7077453Z # must be kept in sync. You can do it easily by running the following command: 2025-11-03T15:43:06.7078479Z # python .github/scripts/update_runner_determinator.py 2025-11-03T15:43:06.7079295Z  2025-11-03T15:43:06.7079784Z """ 2025-11-03T15:43:06.7080506Z This runner determinator is used to determine which set of runners to run a 2025-11-03T15:43:06.7081898Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-11-03T15:43:06.7083186Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-11-03T15:43:06.7084297Z of which runners should be used to run which job. 2025-11-03T15:43:06.7085016Z  2025-11-03T15:43:06.7085776Z The configuration has two parts, the settings and a list of opted-in users, 2025-11-03T15:43:06.7087004Z separated by a line containing "---". If the line is not present, the 2025-11-03T15:43:06.7088094Z settings are considered to be empty with only the second part, the user 2025-11-03T15:43:06.7089036Z list, defined. 2025-11-03T15:43:06.7089533Z  2025-11-03T15:43:06.7090316Z The first part is a YAML block that defines the rollout settings. This can be 2025-11-03T15:43:06.7091847Z used to define any settings that are needed to determine which runners to use. 2025-11-03T15:43:06.7092913Z It's fields are defined by the RolloutSettings class below. 2025-11-03T15:43:06.7093792Z  2025-11-03T15:43:06.7094791Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-11-03T15:43:06.7095891Z The user list is also a comma separated list of additional features or 2025-11-03T15:43:06.7096947Z experiments which the user could be opted in to. 2025-11-03T15:43:06.7097651Z  2025-11-03T15:43:06.7098166Z The user list has the following rules: 2025-11-03T15:43:06.7098859Z  2025-11-03T15:43:06.7099597Z - Users are GitHub usernames, which must start with the @ prefix 2025-11-03T15:43:06.7100652Z - Each user is also a comma-separated list of features/experiments to enable 2025-11-03T15:43:06.7101904Z - A "#" prefix opts the user out of all experiments 2025-11-03T15:43:06.7102628Z  2025-11-03T15:43:06.7103077Z Example config: 2025-11-03T15:43:06.7183939Z  # A list of experiments that can be opted into. 2025-11-03T15:43:06.7184813Z  # This defines the behavior they'll induce when opted into. 2025-11-03T15:43:06.7185556Z  # Expected syntax is: 2025-11-03T15:43:06.7186340Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-11-03T15:43:06.7187479Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-11-03T15:43:06.7188324Z  2025-11-03T15:43:06.7188719Z  experiments: 2025-11-03T15:43:06.7189170Z  lf: 2025-11-03T15:43:06.7189605Z  rollout_percent: 25 2025-11-03T15:43:06.7190151Z  all_branches: false 2025-11-03T15:43:06.7190670Z  default: true 2025-11-03T15:43:06.7191417Z  --- 2025-11-03T15:43:06.7191813Z  2025-11-03T15:43:06.7192190Z  # Opt-ins: 2025-11-03T15:43:06.7192873Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-11-03T15:43:06.7194102Z  # and specifying experiments to enable in a comma-separated list. 2025-11-03T15:43:06.7195008Z  # To always opt out of an experiment, prefix it with a "-". 2025-11-03T15:43:06.7195785Z  # Experiments should be from the above list. 2025-11-03T15:43:06.7196391Z  2025-11-03T15:43:06.7196809Z  @User1,-lf,split_build 2025-11-03T15:43:06.7197334Z  @User2,lf 2025-11-03T15:43:06.7197801Z  @User3,split_build 2025-11-03T15:43:06.7198293Z """ 2025-11-03T15:43:06.7198682Z  2025-11-03T15:43:06.7199063Z import json 2025-11-03T15:43:06.7199496Z import logging 2025-11-03T15:43:06.7199955Z import os 2025-11-03T15:43:06.7200381Z import random 2025-11-03T15:43:06.7200947Z import re 2025-11-03T15:43:06.7201365Z import sys 2025-11-03T15:43:06.7201844Z from argparse import ArgumentParser 2025-11-03T15:43:06.7202543Z from collections.abc import Iterable 2025-11-03T15:43:06.7203148Z from functools import cache 2025-11-03T15:43:06.7203703Z from logging import LogRecord 2025-11-03T15:43:06.7204284Z from typing import Any, NamedTuple 2025-11-03T15:43:06.7204925Z from urllib.request import Request, urlopen 2025-11-03T15:43:06.7205527Z  2025-11-03T15:43:06.7205908Z import yaml 2025-11-03T15:43:06.7206375Z from github import Auth, Github 2025-11-03T15:43:06.7206945Z from github.Issue import Issue 2025-11-03T15:43:06.7207482Z  2025-11-03T15:43:06.7207846Z  2025-11-03T15:43:06.7208326Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-11-03T15:43:06.7209131Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-11-03T15:43:06.7210139Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-11-03T15:43:06.7211042Z  2025-11-03T15:43:06.7211717Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-11-03T15:43:06.7212392Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-11-03T15:43:06.7213006Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-11-03T15:43:06.7213693Z OPT_OUT_LABEL = "no-runner-experiments" 2025-11-03T15:43:06.7214277Z  2025-11-03T15:43:06.7214718Z SETTING_EXPERIMENTS = "experiments" 2025-11-03T15:43:06.7215273Z  2025-11-03T15:43:06.7215680Z LF_FLEET_EXPERIMENT = "lf" 2025-11-03T15:43:06.7216225Z CANARY_FLEET_SUFFIX = ".c" 2025-11-03T15:43:06.7216748Z  2025-11-03T15:43:06.7217125Z  2025-11-03T15:43:06.7217541Z class Experiment(NamedTuple): 2025-11-03T15:43:06.7218127Z  rollout_perc: float = ( 2025-11-03T15:43:06.7218885Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-11-03T15:43:06.7219673Z  ) 2025-11-03T15:43:06.7220110Z  all_branches: bool = ( 2025-11-03T15:43:06.7221014Z  False # If True, the experiment is also enabled on the exception branches 2025-11-03T15:43:06.7221790Z  ) 2025-11-03T15:43:06.7222207Z  default: bool = ( 2025-11-03T15:43:06.7222894Z  True # If True, the experiment is enabled by default for all queries 2025-11-03T15:43:06.7223620Z  ) 2025-11-03T15:43:06.7224018Z  2025-11-03T15:43:06.7224429Z  # Add more fields as needed 2025-11-03T15:43:06.7224958Z  2025-11-03T15:43:06.7225332Z  2025-11-03T15:43:06.7225732Z class Settings(NamedTuple): 2025-11-03T15:43:06.7226262Z  """ 2025-11-03T15:43:06.7226802Z  Settings for the experiments that can be opted into. 2025-11-03T15:43:06.7227469Z  """ 2025-11-03T15:43:06.7227867Z  2025-11-03T15:43:06.7228316Z  experiments: dict[str, Experiment] = {} 2025-11-03T15:43:06.7228904Z  2025-11-03T15:43:06.7229404Z  2025-11-03T15:43:06.7229861Z class ColorFormatter(logging.Formatter): 2025-11-03T15:43:06.7230595Z  """Color codes the log messages based on the log level""" 2025-11-03T15:43:06.7231365Z  2025-11-03T15:43:06.7231746Z  COLORS = { 2025-11-03T15:43:06.7232243Z  "WARNING": "\033[33m", # Yellow 2025-11-03T15:43:06.7232830Z  "ERROR": "\033[31m", # Red 2025-11-03T15:43:06.7233409Z  "CRITICAL": "\033[31m", # Red 2025-11-03T15:43:06.7233992Z  "INFO": "\033[0m", # Reset 2025-11-03T15:43:06.7234565Z  "DEBUG": "\033[0m", # Reset 2025-11-03T15:43:06.7235110Z  } 2025-11-03T15:43:06.7235498Z  2025-11-03T15:43:06.7235960Z  def format(self, record: LogRecord) -> str: 2025-11-03T15:43:06.7236810Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-11-03T15:43:06.7237711Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-11-03T15:43:06.7238376Z  return super().format(record) 2025-11-03T15:43:06.7238933Z  2025-11-03T15:43:06.7239314Z  2025-11-03T15:43:06.7239749Z handler = logging.StreamHandler() 2025-11-03T15:43:06.7240582Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-11-03T15:43:06.7241504Z  2025-11-03T15:43:06.7242018Z log = logging.getLogger(os.path.basename(__file__)) 2025-11-03T15:43:06.7242699Z log.addHandler(handler) 2025-11-03T15:43:06.7243238Z log.setLevel(logging.INFO) 2025-11-03T15:43:06.7243750Z  2025-11-03T15:43:06.7244124Z  2025-11-03T15:43:06.7244631Z def set_github_output(key: str, value: str) -> None: 2025-11-03T15:43:06.7245276Z  """ 2025-11-03T15:43:06.7245860Z  Defines outputs of the github action that invokes this script 2025-11-03T15:43:06.7246711Z  """ 2025-11-03T15:43:06.7247146Z  if not GITHUB_OUTPUT: 2025-11-03T15:43:06.7248334Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-11-03T15:43:06.7249632Z  log.warning( 2025-11-03T15:43:06.7250619Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-11-03T15:43:06.7251764Z  ) 2025-11-03T15:43:06.7252283Z  print(f"::set-output name={key}::{value}") 2025-11-03T15:43:06.7252899Z  return 2025-11-03T15:43:06.7253342Z  2025-11-03T15:43:06.7253775Z  with open(GITHUB_OUTPUT, "a") as f: 2025-11-03T15:43:06.7254446Z  log.info(f"Setting output: {key}='{value}'") 2025-11-03T15:43:06.7255100Z  f.write(f"{key}={value}\n") 2025-11-03T15:43:06.7255655Z  2025-11-03T15:43:06.7256038Z  2025-11-03T15:43:06.7256595Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-11-03T15:43:06.7257347Z  return frozenset( 2025-11-03T15:43:06.7258066Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-11-03T15:43:06.7258840Z  ) 2025-11-03T15:43:06.7259245Z  2025-11-03T15:43:06.7259618Z  2025-11-03T15:43:06.7260025Z def parse_args() -> Any: 2025-11-03T15:43:06.7260835Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-11-03T15:43:06.7261835Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-11-03T15:43:06.7262712Z  parser.add_argument( 2025-11-03T15:43:06.7263267Z  "--github-issue-repo", 2025-11-03T15:43:06.7263822Z  type=str, 2025-11-03T15:43:06.7264321Z  required=False, 2025-11-03T15:43:06.7265000Z  default="pytorch/test-infra", 2025-11-03T15:43:06.7265662Z  help="GitHub repo to get the issue", 2025-11-03T15:43:06.7266252Z  ) 2025-11-03T15:43:06.7266681Z  parser.add_argument( 2025-11-03T15:43:06.7267218Z  "--github-repo", 2025-11-03T15:43:06.7267732Z  type=str, 2025-11-03T15:43:06.7268226Z  required=True, 2025-11-03T15:43:06.7268799Z  help="GitHub repo where CI is running", 2025-11-03T15:43:06.7269390Z  ) 2025-11-03T15:43:06.7269831Z  parser.add_argument( 2025-11-03T15:43:06.7270559Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-11-03T15:43:06.7271491Z  ) 2025-11-03T15:43:06.7271918Z  parser.add_argument( 2025-11-03T15:43:06.7272678Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-11-03T15:43:06.7273466Z  ) 2025-11-03T15:43:06.7273904Z  parser.add_argument( 2025-11-03T15:43:06.7274656Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-11-03T15:43:06.7275440Z  ) 2025-11-03T15:43:06.7275869Z  parser.add_argument( 2025-11-03T15:43:06.7276655Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-11-03T15:43:06.7277463Z  ) 2025-11-03T15:43:06.7277918Z  parser.add_argument( 2025-11-03T15:43:06.7278463Z  "--github-ref-type", 2025-11-03T15:43:06.7279014Z  type=str, 2025-11-03T15:43:06.7279501Z  required=True, 2025-11-03T15:43:06.7280113Z  help="Current GitHub ref type, branch or tag", 2025-11-03T15:43:06.7280838Z  ) 2025-11-03T15:43:06.7281268Z  parser.add_argument( 2025-11-03T15:43:06.7281979Z  "--eligible-experiments", 2025-11-03T15:43:06.7282604Z  type=_str_comma_separated_to_set, 2025-11-03T15:43:06.7283201Z  required=False, 2025-11-03T15:43:06.7283723Z  default="", 2025-11-03T15:43:06.7284695Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-11-03T15:43:06.7285755Z  ) 2025-11-03T15:43:06.7286183Z  parser.add_argument( 2025-11-03T15:43:06.7286771Z  "--opt-out-experiments", 2025-11-03T15:43:06.7287375Z  type=_str_comma_separated_to_set, 2025-11-03T15:43:06.7287976Z  required=False, 2025-11-03T15:43:06.7288492Z  default="", 2025-11-03T15:43:06.7288976Z  help=( 2025-11-03T15:43:06.7289752Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-11-03T15:43:06.7291126Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-11-03T15:43:06.7292088Z  ), 2025-11-03T15:43:06.7292511Z  ) 2025-11-03T15:43:06.7292948Z  parser.add_argument( 2025-11-03T15:43:06.7293491Z  "--pr-number", 2025-11-03T15:43:06.7294001Z  type=str, 2025-11-03T15:43:06.7294494Z  required=False, 2025-11-03T15:43:06.7295004Z  default="", 2025-11-03T15:43:06.7295596Z  help="the optional PR number where this is run", 2025-11-03T15:43:06.7296222Z  ) 2025-11-03T15:43:06.7296623Z  2025-11-03T15:43:06.7297045Z  return parser.parse_args() 2025-11-03T15:43:06.7297597Z  2025-11-03T15:43:06.7297971Z  2025-11-03T15:43:06.7298631Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.7299647Z  auth = Auth.Token(github_token) 2025-11-03T15:43:06.7300271Z  return Github(auth=auth) 2025-11-03T15:43:06.7300950Z  2025-11-03T15:43:06.7301327Z  2025-11-03T15:43:06.7302055Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.7302984Z  repo = gh.get_repo(repo) 2025-11-03T15:43:06.7303592Z  return repo.get_issue(number=issue_num) 2025-11-03T15:43:06.7304199Z  2025-11-03T15:43:06.7304578Z  2025-11-03T15:43:06.7305003Z def get_potential_pr_author( 2025-11-03T15:43:06.7305778Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-11-03T15:43:06.7306562Z ) -> str: 2025-11-03T15:43:06.7307186Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-11-03T15:43:06.7308118Z  # Fetch the actual username from the original PR. The PR number is 2025-11-03T15:43:06.7309005Z  # embedded in the tag name: ciflow// 2025-11-03T15:43:06.7309656Z  2025-11-03T15:43:06.7310096Z  gh = get_gh_client(github_token) 2025-11-03T15:43:06.7310666Z  2025-11-03T15:43:06.7311341Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-11-03T15:43:06.7312078Z  split_tag = ref_name.split("/") 2025-11-03T15:43:06.7312656Z  if ( 2025-11-03T15:43:06.7313120Z  len(split_tag) == 3 2025-11-03T15:43:06.7313701Z  and split_tag[0] == "ciflow" 2025-11-03T15:43:06.7314495Z  and split_tag[2].isnumeric() 2025-11-03T15:43:06.7315209Z  ): 2025-11-03T15:43:06.7315682Z  pr_number = split_tag[2] 2025-11-03T15:43:06.7316246Z  try: 2025-11-03T15:43:06.7316774Z  repository = gh.get_repo(repo) 2025-11-03T15:43:06.7317662Z  pull = repository.get_pull(number=int(pr_number)) 2025-11-03T15:43:06.7318373Z  except Exception as e: 2025-11-03T15:43:06.7318992Z  raise Exception( # noqa: TRY002 2025-11-03T15:43:06.7319764Z  f"issue with pull request {pr_number} from repo {repository}" 2025-11-03T15:43:06.7320500Z  ) from e 2025-11-03T15:43:06.7321272Z  return pull.user.login # type: ignore[no-any-return] 2025-11-03T15:43:06.7322085Z  # In all other cases, return the original input username 2025-11-03T15:43:06.7322770Z  return username 2025-11-03T15:43:06.7323250Z  2025-11-03T15:43:06.7323632Z  2025-11-03T15:43:06.7324106Z def is_exception_branch(branch: str) -> bool: 2025-11-03T15:43:06.7324718Z  """ 2025-11-03T15:43:06.7325465Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-11-03T15:43:06.7326365Z  """ 2025-11-03T15:43:06.7326992Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-11-03T15:43:06.7327739Z  2025-11-03T15:43:06.7328121Z  2025-11-03T15:43:06.7328553Z def load_yaml(yaml_text: str) -> Any: 2025-11-03T15:43:06.7329136Z  try: 2025-11-03T15:43:06.7329599Z  data = yaml.safe_load(yaml_text) 2025-11-03T15:43:06.7330200Z  return data 2025-11-03T15:43:06.7330811Z  except yaml.YAMLError: 2025-11-03T15:43:06.7331415Z  log.exception("Error loading YAML") 2025-11-03T15:43:06.7332000Z  raise 2025-11-03T15:43:06.7332435Z  2025-11-03T15:43:06.7332806Z  2025-11-03T15:43:06.7333482Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-11-03T15:43:06.7334324Z  """ 2025-11-03T15:43:06.7335162Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-11-03T15:43:06.7336046Z  2025-11-03T15:43:06.7336660Z  If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.7337544Z  and the text below is the list of opted in users. 2025-11-03T15:43:06.7338185Z  2025-11-03T15:43:06.7338820Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-11-03T15:43:06.7339613Z  """ 2025-11-03T15:43:06.7340139Z  rollout_state_parts = rollout_state.split("---") 2025-11-03T15:43:06.7340977Z  if len(rollout_state_parts) >= 2: 2025-11-03T15:43:06.7341702Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-11-03T15:43:06.7342371Z  else: 2025-11-03T15:43:06.7342827Z  return "", rollout_state 2025-11-03T15:43:06.7343366Z  2025-11-03T15:43:06.7343737Z  2025-11-03T15:43:06.7344183Z class UserOptins(dict[str, list[str]]): 2025-11-03T15:43:06.7344770Z  """ 2025-11-03T15:43:06.7345372Z  Dictionary of users with a list of features they have opted into 2025-11-03T15:43:06.7346098Z  """ 2025-11-03T15:43:06.7346497Z  2025-11-03T15:43:06.7346864Z  2025-11-03T15:43:06.7347451Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-11-03T15:43:06.7348207Z  """ 2025-11-03T15:43:06.7349012Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-11-03T15:43:06.7349963Z  2025-11-03T15:43:06.7351383Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-11-03T15:43:06.7352574Z  - Example line: "@User1,lf,split_build" 2025-11-03T15:43:06.7353526Z  - A "#" prefix indicates the user is opted out of all experiments 2025-11-03T15:43:06.7354258Z  2025-11-03T15:43:06.7354626Z  2025-11-03T15:43:06.7355000Z  """ 2025-11-03T15:43:06.7355426Z  optins = UserOptins() 2025-11-03T15:43:06.7356020Z  for user in user_optin_text.split("\n"): 2025-11-03T15:43:06.7356665Z  user = user.strip("\r\n\t -") 2025-11-03T15:43:06.7357296Z  if not user or not user.startswith("@"): 2025-11-03T15:43:06.7357937Z  # Not a valid user. Skip 2025-11-03T15:43:06.7358503Z  continue 2025-11-03T15:43:06.7359009Z  2025-11-03T15:43:06.7359396Z  if user: 2025-11-03T15:43:06.7359934Z  usr_name = user.split(",")[0].strip("@") 2025-11-03T15:43:06.7360825Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-11-03T15:43:06.7361553Z  2025-11-03T15:43:06.7361950Z  return optins 2025-11-03T15:43:06.7362412Z  2025-11-03T15:43:06.7362781Z  2025-11-03T15:43:06.7363332Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-11-03T15:43:06.7364029Z  """ 2025-11-03T15:43:06.7364496Z  Check if the experiment name is valid. 2025-11-03T15:43:06.7365095Z  A valid name: 2025-11-03T15:43:06.7365842Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-11-03T15:43:06.7366885Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-11-03T15:43:06.7367693Z  - Cannot contain spaces 2025-11-03T15:43:06.7368227Z  """ 2025-11-03T15:43:06.7368619Z  2025-11-03T15:43:06.7369125Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-11-03T15:43:06.7369933Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-11-03T15:43:06.7370995Z  2025-11-03T15:43:06.7371489Z  if valid: 2025-11-03T15:43:06.7371953Z  return True 2025-11-03T15:43:06.7372416Z  2025-11-03T15:43:06.7372798Z  log.error( 2025-11-03T15:43:06.7374376Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-11-03T15:43:06.7376140Z  ) 2025-11-03T15:43:06.7376552Z  return False 2025-11-03T15:43:06.7377006Z  2025-11-03T15:43:06.7377381Z  2025-11-03T15:43:06.7377938Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-11-03T15:43:06.7378669Z  """ 2025-11-03T15:43:06.7379346Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-11-03T15:43:06.7380167Z  """ 2025-11-03T15:43:06.7380577Z  try: 2025-11-03T15:43:06.7381106Z  if settings_text: 2025-11-03T15:43:06.7381941Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-11-03T15:43:06.7382827Z  # for easy reading 2025-11-03T15:43:06.7383759Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-11-03T15:43:06.7384766Z  # the backtick character in shell commands. 2025-11-03T15:43:06.7385459Z  backtick = chr(96) # backtick character 2025-11-03T15:43:06.7386248Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-11-03T15:43:06.7387063Z  settings = load_yaml(settings_text) 2025-11-03T15:43:06.7387648Z  2025-11-03T15:43:06.7388300Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-11-03T15:43:06.7389300Z  experiments = {} 2025-11-03T15:43:06.7389819Z  2025-11-03T15:43:06.7390443Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-11-03T15:43:06.7391425Z  if not is_valid_experiment_name(exp_name): 2025-11-03T15:43:06.7392645Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-11-03T15:43:06.7393848Z  continue 2025-11-03T15:43:06.7394371Z  2025-11-03T15:43:06.7394784Z  valid_settings = {} 2025-11-03T15:43:06.7395396Z  for setting in exp_settings: 2025-11-03T15:43:06.7396049Z  if setting not in Experiment._fields: 2025-11-03T15:43:06.7396699Z  log.warning( 2025-11-03T15:43:06.7397513Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-11-03T15:43:06.7398326Z  ) 2025-11-03T15:43:06.7398837Z  else: 2025-11-03T15:43:06.7399450Z  valid_settings[setting] = exp_settings[setting] 2025-11-03T15:43:06.7400106Z  2025-11-03T15:43:06.7400631Z  experiments[exp_name] = Experiment(**valid_settings) 2025-11-03T15:43:06.7401461Z  return Settings(experiments) 2025-11-03T15:43:06.7402028Z  2025-11-03T15:43:06.7402429Z  except Exception: 2025-11-03T15:43:06.7403012Z  log.exception("Failed to parse settings") 2025-11-03T15:43:06.7403615Z  2025-11-03T15:43:06.7404014Z  return Settings() 2025-11-03T15:43:06.7404492Z  2025-11-03T15:43:06.7404870Z  2025-11-03T15:43:06.7405515Z def parse_settings(rollout_state: str) -> Settings: 2025-11-03T15:43:06.7406189Z  """ 2025-11-03T15:43:06.7406702Z  Parse settings, if any, from the rollout state. 2025-11-03T15:43:06.7407330Z  2025-11-03T15:43:06.7407930Z  If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.7408796Z  and the text below is the list of opted in users. 2025-11-03T15:43:06.7409431Z  2025-11-03T15:43:06.7410077Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-11-03T15:43:06.7411029Z  """ 2025-11-03T15:43:06.7411662Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.7412536Z  return parse_settings_from_text(settings_text) 2025-11-03T15:43:06.7413158Z  2025-11-03T15:43:06.7413529Z  2025-11-03T15:43:06.7414042Z def parse_users(rollout_state: str) -> UserOptins: 2025-11-03T15:43:06.7414678Z  """ 2025-11-03T15:43:06.7415149Z  Parse users from the rollout state. 2025-11-03T15:43:06.7415720Z  2025-11-03T15:43:06.7416095Z  """ 2025-11-03T15:43:06.7416703Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.7417568Z  return parse_user_opt_in_from_text(users_text) 2025-11-03T15:43:06.7418191Z  2025-11-03T15:43:06.7418557Z  2025-11-03T15:43:06.7419243Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.7420106Z  """ 2025-11-03T15:43:06.7420599Z  Check if a user is opted into an experiment 2025-11-03T15:43:06.7421307Z  """ 2025-11-03T15:43:06.7421844Z  return experiment_name in user_optins.get(user, []) 2025-11-03T15:43:06.7422500Z  2025-11-03T15:43:06.7423007Z  2025-11-03T15:43:06.7423711Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.7424575Z  """ 2025-11-03T15:43:06.7425116Z  Check if a user explicitly opted out of an experiment 2025-11-03T15:43:06.7425765Z  """ 2025-11-03T15:43:06.7426355Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-11-03T15:43:06.7427154Z  experiment_optout = "-" + experiment_name 2025-11-03T15:43:06.7427888Z  if experiment_optout not in user_optins.get(user, []): 2025-11-03T15:43:06.7428583Z  return False 2025-11-03T15:43:06.7429061Z  2025-11-03T15:43:06.7429578Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-11-03T15:43:06.7430259Z  log.warning( 2025-11-03T15:43:06.7431299Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-11-03T15:43:06.7432297Z  ) 2025-11-03T15:43:06.7432706Z  2025-11-03T15:43:06.7433106Z  return True 2025-11-03T15:43:06.7433553Z  2025-11-03T15:43:06.7433925Z  2025-11-03T15:43:06.7434330Z def get_runner_prefix( 2025-11-03T15:43:06.7434850Z  rollout_state: str, 2025-11-03T15:43:06.7435421Z  workflow_requestors: Iterable[str], 2025-11-03T15:43:06.7436019Z  branch: str, 2025-11-03T15:43:06.7436620Z  eligible_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.7437397Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.7438067Z  is_canary: bool = False, 2025-11-03T15:43:06.7438592Z ) -> str: 2025-11-03T15:43:06.7439101Z  settings = parse_settings(rollout_state) 2025-11-03T15:43:06.7439767Z  user_optins = parse_users(rollout_state) 2025-11-03T15:43:06.7440376Z  2025-11-03T15:43:06.7441018Z  fleet_prefix = "" 2025-11-03T15:43:06.7441566Z  prefixes = [] 2025-11-03T15:43:06.7442327Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-11-03T15:43:06.7443401Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-11-03T15:43:06.7444212Z  log.info( 2025-11-03T15:43:06.7444996Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-11-03T15:43:06.7445842Z  ) 2025-11-03T15:43:06.7446309Z  continue 2025-11-03T15:43:06.7446774Z  2025-11-03T15:43:06.7447193Z  if opt_out_experiments: 2025-11-03T15:43:06.7447828Z  if experiment_name in opt_out_experiments: 2025-11-03T15:43:06.7448571Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-11-03T15:43:06.7449241Z  log.info( 2025-11-03T15:43:06.7450284Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-11-03T15:43:06.7451496Z  ) 2025-11-03T15:43:06.7451969Z  continue 2025-11-03T15:43:06.7452467Z  2025-11-03T15:43:06.7452940Z  if eligible_experiments: 2025-11-03T15:43:06.7453603Z  if experiment_name not in eligible_experiments: 2025-11-03T15:43:06.7454328Z  exp_list = ", ".join(eligible_experiments) 2025-11-03T15:43:06.7454961Z  log.info( 2025-11-03T15:43:06.7455850Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-11-03T15:43:06.7456785Z  ) 2025-11-03T15:43:06.7457410Z  continue 2025-11-03T15:43:06.7457990Z  elif not experiment_settings.default: 2025-11-03T15:43:06.7458600Z  log.info( 2025-11-03T15:43:06.7459367Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-11-03T15:43:06.7460213Z  ) 2025-11-03T15:43:06.7460768Z  continue 2025-11-03T15:43:06.7461248Z  2025-11-03T15:43:06.7461775Z  # Is any workflow_requestor opted out to this experiment? 2025-11-03T15:43:06.7462477Z  opted_out_users = [ 2025-11-03T15:43:06.7463031Z  requestor 2025-11-03T15:43:06.7463587Z  for requestor in workflow_requestors 2025-11-03T15:43:06.7464368Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.7465083Z  ] 2025-11-03T15:43:06.7465494Z  2025-11-03T15:43:06.7465905Z  if opted_out_users: 2025-11-03T15:43:06.7466467Z  log.info( 2025-11-03T15:43:06.7467248Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-11-03T15:43:06.7468034Z  ) 2025-11-03T15:43:06.7468485Z  continue 2025-11-03T15:43:06.7468949Z  2025-11-03T15:43:06.7469468Z  # Is any workflow_requestor opted in to this experiment? 2025-11-03T15:43:06.7470161Z  opted_in_users = [ 2025-11-03T15:43:06.7470802Z  requestor 2025-11-03T15:43:06.7471369Z  for requestor in workflow_requestors 2025-11-03T15:43:06.7472139Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.7472847Z  ] 2025-11-03T15:43:06.7473256Z  2025-11-03T15:43:06.7473657Z  enabled = False 2025-11-03T15:43:06.7474188Z  if opted_in_users: 2025-11-03T15:43:06.7474839Z  log.info( 2025-11-03T15:43:06.7475576Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-11-03T15:43:06.7476343Z  ) 2025-11-03T15:43:06.7476801Z  enabled = True 2025-11-03T15:43:06.7477313Z  2025-11-03T15:43:06.7477768Z  elif experiment_settings.rollout_perc: 2025-11-03T15:43:06.7478690Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-11-03T15:43:06.7479749Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-11-03T15:43:06.7480481Z  log.info( 2025-11-03T15:43:06.7481560Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-11-03T15:43:06.7482599Z  ) 2025-11-03T15:43:06.7483104Z  enabled = True 2025-11-03T15:43:06.7483627Z  2025-11-03T15:43:06.7484018Z  if enabled: 2025-11-03T15:43:06.7484529Z  label = experiment_name 2025-11-03T15:43:06.7485176Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-11-03T15:43:06.7486120Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-11-03T15:43:06.7487154Z  # - If it's enabled, then we always list it's prefix first 2025-11-03T15:43:06.7488023Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-11-03T15:43:06.7488767Z  if is_canary: 2025-11-03T15:43:06.7489360Z  label += CANARY_FLEET_SUFFIX 2025-11-03T15:43:06.7489979Z  fleet_prefix = label 2025-11-03T15:43:06.7490545Z  else: 2025-11-03T15:43:06.7491323Z  prefixes.append(label) 2025-11-03T15:43:06.7491899Z  2025-11-03T15:43:06.7492303Z  if len(prefixes) > 1: 2025-11-03T15:43:06.7492827Z  log.error( 2025-11-03T15:43:06.7493974Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-11-03T15:43:06.7495225Z  ) 2025-11-03T15:43:06.7495683Z  prefixes = prefixes[:1] 2025-11-03T15:43:06.7496217Z  2025-11-03T15:43:06.7496625Z  # Fleet always comes first 2025-11-03T15:43:06.7497178Z  if fleet_prefix: 2025-11-03T15:43:06.7497711Z  prefixes.insert(0, fleet_prefix) 2025-11-03T15:43:06.7498280Z  2025-11-03T15:43:06.7498778Z  return ".".join(prefixes) + "." if prefixes else "" 2025-11-03T15:43:06.7499432Z  2025-11-03T15:43:06.7499804Z  2025-11-03T15:43:06.7500510Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-11-03T15:43:06.7501495Z  """ 2025-11-03T15:43:06.7502162Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-11-03T15:43:06.7502975Z  2025-11-03T15:43:06.7503606Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-11-03T15:43:06.7504408Z  """ 2025-11-03T15:43:06.7504859Z  gh = get_gh_client(github_token) 2025-11-03T15:43:06.7505505Z  issue = get_issue(gh, repo, issue_num) 2025-11-03T15:43:06.7506240Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-11-03T15:43:06.7506916Z  2025-11-03T15:43:06.7507293Z  2025-11-03T15:43:06.7507940Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-11-03T15:43:06.7508945Z  for _ in range(num_retries): 2025-11-03T15:43:06.7509499Z  try: 2025-11-03T15:43:06.7510013Z  req = Request(url=url, headers=headers) 2025-11-03T15:43:06.7510867Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-11-03T15:43:06.7511611Z  return json.loads(content) 2025-11-03T15:43:06.7512221Z  except Exception as e: 2025-11-03T15:43:06.7512853Z  log.warning(f"Could not download {url}: {e}") 2025-11-03T15:43:06.7513468Z  2025-11-03T15:43:06.7514092Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-11-03T15:43:06.7514907Z  return {} 2025-11-03T15:43:06.7515344Z  2025-11-03T15:43:06.7515718Z  2025-11-03T15:43:06.7516092Z @cache 2025-11-03T15:43:06.7516802Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-11-03T15:43:06.7517673Z  """ 2025-11-03T15:43:06.7518129Z  Dynamically get PR information 2025-11-03T15:43:06.7518696Z  """ 2025-11-03T15:43:06.7519265Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-11-03T15:43:06.7519980Z  headers = { 2025-11-03T15:43:06.7520527Z  "Accept": "application/vnd.github.v3+json", 2025-11-03T15:43:06.7521334Z  "Authorization": f"token {github_token}", 2025-11-03T15:43:06.7521934Z  } 2025-11-03T15:43:06.7522427Z  json_response: dict[str, Any] = download_json( 2025-11-03T15:43:06.7523127Z  url=f"{github_api}/issues/{pr_number}", 2025-11-03T15:43:06.7523736Z  headers=headers, 2025-11-03T15:43:06.7524248Z  ) 2025-11-03T15:43:06.7524641Z  2025-11-03T15:43:06.7525046Z  if not json_response: 2025-11-03T15:43:06.7525721Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-11-03T15:43:06.7526579Z  return {} 2025-11-03T15:43:06.7527057Z  2025-11-03T15:43:06.7527461Z  return json_response 2025-11-03T15:43:06.7527974Z  2025-11-03T15:43:06.7528340Z  2025-11-03T15:43:06.7529004Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-11-03T15:43:06.7529824Z  """ 2025-11-03T15:43:06.7530457Z  Dynamically get the latest list of labels from the pull request 2025-11-03T15:43:06.7531305Z  """ 2025-11-03T15:43:06.7531866Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-11-03T15:43:06.7532564Z  return { 2025-11-03T15:43:06.7533228Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-11-03T15:43:06.7534010Z  } 2025-11-03T15:43:06.7534402Z  2025-11-03T15:43:06.7534787Z  2025-11-03T15:43:06.7535185Z def main() -> None: 2025-11-03T15:43:06.7535686Z  args = parse_args() 2025-11-03T15:43:06.7536190Z  2025-11-03T15:43:06.7536655Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-11-03T15:43:06.7537264Z  2025-11-03T15:43:06.7537679Z  # Check if the PR is opt-out 2025-11-03T15:43:06.7538254Z  if args.pr_number: 2025-11-03T15:43:06.7539017Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-11-03T15:43:06.7539889Z  if OPT_OUT_LABEL in labels: 2025-11-03T15:43:06.7540467Z  log.info( 2025-11-03T15:43:06.7541358Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-11-03T15:43:06.7542218Z  ) 2025-11-03T15:43:06.7542867Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.7543649Z  sys.exit() 2025-11-03T15:43:06.7544252Z  2025-11-03T15:43:06.7544643Z  try: 2025-11-03T15:43:06.7545153Z  rollout_state = get_rollout_state_from_issue( 2025-11-03T15:43:06.7545956Z  args.github_token, args.github_issue_repo, args.github_issue 2025-11-03T15:43:06.7546679Z  ) 2025-11-03T15:43:06.7547084Z  2025-11-03T15:43:06.7547525Z  username = get_potential_pr_author( 2025-11-03T15:43:06.7548137Z  args.github_token, 2025-11-03T15:43:06.7548714Z  args.github_repo, 2025-11-03T15:43:06.7549335Z  args.github_actor, 2025-11-03T15:43:06.7549915Z  args.github_ref_type, 2025-11-03T15:43:06.7550510Z  args.github_branch, 2025-11-03T15:43:06.7551158Z  ) 2025-11-03T15:43:06.7551575Z  2025-11-03T15:43:06.7552106Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-11-03T15:43:06.7552791Z  2025-11-03T15:43:06.7553246Z  runner_label_prefix = get_runner_prefix( 2025-11-03T15:43:06.7553877Z  rollout_state, 2025-11-03T15:43:06.7554461Z  (args.github_issue_owner, username), 2025-11-03T15:43:06.7555085Z  args.github_branch, 2025-11-03T15:43:06.7555685Z  args.eligible_experiments, 2025-11-03T15:43:06.7556304Z  args.opt_out_experiments, 2025-11-03T15:43:06.7556890Z  is_canary, 2025-11-03T15:43:06.7557380Z  ) 2025-11-03T15:43:06.7557799Z  2025-11-03T15:43:06.7558203Z  except Exception as e: 2025-11-03T15:43:06.7558737Z  log.error( 2025-11-03T15:43:06.7559540Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-11-03T15:43:06.7560554Z  ) 2025-11-03T15:43:06.7561081Z  2025-11-03T15:43:06.7561663Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.7562418Z  2025-11-03T15:43:06.7562784Z  2025-11-03T15:43:06.7563203Z if __name__ == "__main__": 2025-11-03T15:43:06.7563725Z  main() 2025-11-03T15:43:06.7564157Z  2025-11-03T15:43:06.7564537Z EOF 2025-11-03T15:43:06.7564915Z  2025-11-03T15:43:06.7565329Z cat runner_determinator.py 2025-11-03T15:43:06.8222568Z shell: /usr/bin/bash -e {0} 2025-11-03T15:43:06.8223465Z env: 2025-11-03T15:43:06.8224269Z GITHUB_TOKEN: *** 2025-11-03T15:43:06.8224710Z ISSUE_NUMBER: 5132 2025-11-03T15:43:06.8225185Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:43:06.8225724Z ISSUE_OWNER: 2025-11-03T15:43:06.8226149Z CHECK_EXPERIMENTS: 2025-11-03T15:43:06.8226602Z OPT_OUT_EXPERIMENTS: lf 2025-11-03T15:43:06.8227061Z PR_NUMBER: 2025-11-03T15:43:06.8227494Z ##[endgroup] 2025-11-03T15:43:06.8435214Z # flake8: noqa: G004 2025-11-03T15:43:06.8435572Z 2025-11-03T15:43:06.8436021Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-11-03T15:43:06.8437041Z # must be kept in sync. You can do it easily by running the following command: 2025-11-03T15:43:06.8437844Z # python .github/scripts/update_runner_determinator.py 2025-11-03T15:43:06.8438305Z 2025-11-03T15:43:06.8438465Z """ 2025-11-03T15:43:06.8439052Z This runner determinator is used to determine which set of runners to run a 2025-11-03T15:43:06.8439941Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-11-03T15:43:06.8441178Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-11-03T15:43:06.8442021Z of which runners should be used to run which job. 2025-11-03T15:43:06.8442440Z 2025-11-03T15:43:06.8442828Z The configuration has two parts, the settings and a list of opted-in users, 2025-11-03T15:43:06.8443936Z separated by a line containing "---". If the line is not present, the 2025-11-03T15:43:06.8444836Z settings are considered to be empty with only the second part, the user 2025-11-03T15:43:06.8445546Z list, defined. 2025-11-03T15:43:06.8445775Z 2025-11-03T15:43:06.8446149Z The first part is a YAML block that defines the rollout settings. This can be 2025-11-03T15:43:06.8447098Z used to define any settings that are needed to determine which runners to use. 2025-11-03T15:43:06.8447929Z It's fields are defined by the RolloutSettings class below. 2025-11-03T15:43:06.8448381Z 2025-11-03T15:43:06.8448752Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-11-03T15:43:06.8449629Z The user list is also a comma separated list of additional features or 2025-11-03T15:43:06.8450369Z experiments which the user could be opted in to. 2025-11-03T15:43:06.8450996Z 2025-11-03T15:43:06.8451230Z The user list has the following rules: 2025-11-03T15:43:06.8451600Z 2025-11-03T15:43:06.8451925Z - Users are GitHub usernames, which must start with the @ prefix 2025-11-03T15:43:06.8452815Z - Each user is also a comma-separated list of features/experiments to enable 2025-11-03T15:43:06.8453586Z - A "#" prefix opts the user out of all experiments 2025-11-03T15:43:06.8453994Z 2025-11-03T15:43:06.8454166Z Example config: 2025-11-03T15:43:06.8454634Z # A list of experiments that can be opted into. 2025-11-03T15:43:06.8455351Z # This defines the behavior they'll induce when opted into. 2025-11-03T15:43:06.8456006Z # Expected syntax is: 2025-11-03T15:43:06.8456649Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-11-03T15:43:06.8457640Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-11-03T15:43:06.8458250Z 2025-11-03T15:43:06.8458429Z experiments: 2025-11-03T15:43:06.8458814Z lf: 2025-11-03T15:43:06.8459200Z rollout_percent: 25 2025-11-03T15:43:06.8459833Z all_branches: false 2025-11-03T15:43:06.8460295Z default: true 2025-11-03T15:43:06.8461243Z --- 2025-11-03T15:43:06.8461492Z 2025-11-03T15:43:06.8461670Z # Opt-ins: 2025-11-03T15:43:06.8462276Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-11-03T15:43:06.8463182Z # and specifying experiments to enable in a comma-separated list. 2025-11-03T15:43:06.8463955Z # To always opt out of an experiment, prefix it with a "-". 2025-11-03T15:43:06.8464643Z # Experiments should be from the above list. 2025-11-03T15:43:06.8465033Z 2025-11-03T15:43:06.8465229Z @User1,-lf,split_build 2025-11-03T15:43:06.8465674Z @User2,lf 2025-11-03T15:43:06.8466066Z @User3,split_build 2025-11-03T15:43:06.8466477Z """ 2025-11-03T15:43:06.8466684Z 2025-11-03T15:43:06.8466851Z import json 2025-11-03T15:43:06.8467224Z import logging 2025-11-03T15:43:06.8467612Z import os 2025-11-03T15:43:06.8467980Z import random 2025-11-03T15:43:06.8468377Z import re 2025-11-03T15:43:06.8468734Z import sys 2025-11-03T15:43:06.8469150Z from argparse import ArgumentParser 2025-11-03T15:43:06.8469696Z from collections.abc import Iterable 2025-11-03T15:43:06.8470219Z from functools import cache 2025-11-03T15:43:06.8470862Z from logging import LogRecord 2025-11-03T15:43:06.8471491Z from typing import Any, NamedTuple 2025-11-03T15:43:06.8472042Z from urllib.request import Request, urlopen 2025-11-03T15:43:06.8472421Z 2025-11-03T15:43:06.8472589Z import yaml 2025-11-03T15:43:06.8472989Z from github import Auth, Github 2025-11-03T15:43:06.8473484Z from github.Issue import Issue 2025-11-03T15:43:06.8473806Z 2025-11-03T15:43:06.8473814Z 2025-11-03T15:43:06.8474034Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-11-03T15:43:06.8474738Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-11-03T15:43:06.8475626Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-11-03T15:43:06.8476195Z 2025-11-03T15:43:06.8476440Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-11-03T15:43:06.8477163Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-11-03T15:43:06.8477698Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-11-03T15:43:06.8478255Z OPT_OUT_LABEL = "no-runner-experiments" 2025-11-03T15:43:06.8478623Z 2025-11-03T15:43:06.8478822Z SETTING_EXPERIMENTS = "experiments" 2025-11-03T15:43:06.8479161Z 2025-11-03T15:43:06.8479354Z LF_FLEET_EXPERIMENT = "lf" 2025-11-03T15:43:06.8479826Z CANARY_FLEET_SUFFIX = ".c" 2025-11-03T15:43:06.8480118Z 2025-11-03T15:43:06.8480134Z 2025-11-03T15:43:06.8480334Z class Experiment(NamedTuple): 2025-11-03T15:43:06.8481157Z rollout_perc: float = ( 2025-11-03T15:43:06.8481821Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-11-03T15:43:06.8482511Z ) 2025-11-03T15:43:06.8482887Z all_branches: bool = ( 2025-11-03T15:43:06.8483520Z False # If True, the experiment is also enabled on the exception branches 2025-11-03T15:43:06.8484193Z ) 2025-11-03T15:43:06.8484568Z default: bool = ( 2025-11-03T15:43:06.8485146Z True # If True, the experiment is enabled by default for all queries 2025-11-03T15:43:06.8485790Z ) 2025-11-03T15:43:06.8485985Z 2025-11-03T15:43:06.8486168Z # Add more fields as needed 2025-11-03T15:43:06.8486481Z 2025-11-03T15:43:06.8486489Z 2025-11-03T15:43:06.8486677Z class Settings(NamedTuple): 2025-11-03T15:43:06.8487119Z """ 2025-11-03T15:43:06.8487571Z Settings for the experiments that can be opted into. 2025-11-03T15:43:06.8488143Z """ 2025-11-03T15:43:06.8488337Z 2025-11-03T15:43:06.8488550Z experiments: dict[str, Experiment] = {} 2025-11-03T15:43:06.8488917Z 2025-11-03T15:43:06.8488931Z 2025-11-03T15:43:06.8489150Z class ColorFormatter(logging.Formatter): 2025-11-03T15:43:06.8489779Z """Color codes the log messages based on the log level""" 2025-11-03T15:43:06.8490223Z 2025-11-03T15:43:06.8490386Z COLORS = { 2025-11-03T15:43:06.8490997Z "WARNING": "\033[33m", # Yellow 2025-11-03T15:43:06.8491711Z "ERROR": "\033[31m", # Red 2025-11-03T15:43:06.8492246Z "CRITICAL": "\033[31m", # Red 2025-11-03T15:43:06.8492759Z "INFO": "\033[0m", # Reset 2025-11-03T15:43:06.8493255Z "DEBUG": "\033[0m", # Reset 2025-11-03T15:43:06.8493725Z } 2025-11-03T15:43:06.8493924Z 2025-11-03T15:43:06.8494148Z def format(self, record: LogRecord) -> str: 2025-11-03T15:43:06.8494908Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-11-03T15:43:06.8495699Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-11-03T15:43:06.8496283Z return super().format(record) 2025-11-03T15:43:06.8496624Z 2025-11-03T15:43:06.8496631Z 2025-11-03T15:43:06.8496825Z handler = logging.StreamHandler() 2025-11-03T15:43:06.8497539Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-11-03T15:43:06.8498099Z 2025-11-03T15:43:06.8498348Z log = logging.getLogger(os.path.basename(__file__)) 2025-11-03T15:43:06.8498946Z log.addHandler(handler) 2025-11-03T15:43:06.8499396Z log.setLevel(logging.INFO) 2025-11-03T15:43:06.8499697Z 2025-11-03T15:43:06.8499705Z 2025-11-03T15:43:06.8499958Z def set_github_output(key: str, value: str) -> None: 2025-11-03T15:43:06.8500528Z """ 2025-11-03T15:43:06.8501221Z Defines outputs of the github action that invokes this script 2025-11-03T15:43:06.8501866Z """ 2025-11-03T15:43:06.8502236Z if not GITHUB_OUTPUT: 2025-11-03T15:43:06.8503323Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-11-03T15:43:06.8504447Z log.warning( 2025-11-03T15:43:06.8505313Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-11-03T15:43:06.8506256Z ) 2025-11-03T15:43:06.8516620Z print(f"::set-output name={key}::{value}") 2025-11-03T15:43:06.8517242Z return 2025-11-03T15:43:06.8517499Z 2025-11-03T15:43:06.8517877Z with open(GITHUB_OUTPUT, "a") as f: 2025-11-03T15:43:06.8518499Z log.info(f"Setting output: {key}='{value}'") 2025-11-03T15:43:06.8519088Z f.write(f"{key}={value}\n") 2025-11-03T15:43:06.8519427Z 2025-11-03T15:43:06.8519435Z 2025-11-03T15:43:06.8519742Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-11-03T15:43:06.8520379Z return frozenset( 2025-11-03T15:43:06.8521290Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-11-03T15:43:06.8522003Z ) 2025-11-03T15:43:06.8522203Z 2025-11-03T15:43:06.8522212Z 2025-11-03T15:43:06.8522428Z def parse_args() -> Any: 2025-11-03T15:43:06.8523004Z parser = ArgumentParser("Get dynamic rollout settings") 2025-11-03T15:43:06.8523879Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-11-03T15:43:06.8524654Z parser.add_argument( 2025-11-03T15:43:06.8525124Z "--github-issue-repo", 2025-11-03T15:43:06.8525597Z type=str, 2025-11-03T15:43:06.8526006Z required=False, 2025-11-03T15:43:06.8526462Z default="pytorch/test-infra", 2025-11-03T15:43:06.8527021Z help="GitHub repo to get the issue", 2025-11-03T15:43:06.8527541Z ) 2025-11-03T15:43:06.8527921Z parser.add_argument( 2025-11-03T15:43:06.8528377Z "--github-repo", 2025-11-03T15:43:06.8528819Z type=str, 2025-11-03T15:43:06.8529220Z required=True, 2025-11-03T15:43:06.8579796Z help="GitHub repo where CI is running", 2025-11-03T15:43:06.8581179Z ) 2025-11-03T15:43:06.8581911Z parser.add_argument( 2025-11-03T15:43:06.8582716Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-11-03T15:43:06.8583393Z ) 2025-11-03T15:43:06.8583768Z parser.add_argument( 2025-11-03T15:43:06.8584414Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-11-03T15:43:06.8585091Z ) 2025-11-03T15:43:06.8585455Z parser.add_argument( 2025-11-03T15:43:06.8586357Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-11-03T15:43:06.8587086Z ) 2025-11-03T15:43:06.8587452Z parser.add_argument( 2025-11-03T15:43:06.8588117Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-11-03T15:43:06.8588842Z ) 2025-11-03T15:43:06.8589222Z parser.add_argument( 2025-11-03T15:43:06.8589683Z "--github-ref-type", 2025-11-03T15:43:06.8590145Z type=str, 2025-11-03T15:43:06.8590543Z required=True, 2025-11-03T15:43:06.8591375Z help="Current GitHub ref type, branch or tag", 2025-11-03T15:43:06.8591933Z ) 2025-11-03T15:43:06.8592307Z parser.add_argument( 2025-11-03T15:43:06.8592783Z "--eligible-experiments", 2025-11-03T15:43:06.8593306Z type=_str_comma_separated_to_set, 2025-11-03T15:43:06.8593833Z required=False, 2025-11-03T15:43:06.8594256Z default="", 2025-11-03T15:43:06.8595126Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-11-03T15:43:06.8596050Z ) 2025-11-03T15:43:06.8596420Z parser.add_argument( 2025-11-03T15:43:06.8596881Z "--opt-out-experiments", 2025-11-03T15:43:06.8597393Z type=_str_comma_separated_to_set, 2025-11-03T15:43:06.8597911Z required=False, 2025-11-03T15:43:06.8598335Z default="", 2025-11-03T15:43:06.8598730Z help=( 2025-11-03T15:43:06.8599397Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-11-03T15:43:06.8600548Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-11-03T15:43:06.8601627Z ), 2025-11-03T15:43:06.8601990Z ) 2025-11-03T15:43:06.8602353Z parser.add_argument( 2025-11-03T15:43:06.8602810Z "--pr-number", 2025-11-03T15:43:06.8603221Z type=str, 2025-11-03T15:43:06.8603631Z required=False, 2025-11-03T15:43:06.8604064Z default="", 2025-11-03T15:43:06.8604686Z help="the optional PR number where this is run", 2025-11-03T15:43:06.8605266Z ) 2025-11-03T15:43:06.8605468Z 2025-11-03T15:43:06.8605660Z return parser.parse_args() 2025-11-03T15:43:06.8605974Z 2025-11-03T15:43:06.8605981Z 2025-11-03T15:43:06.8606397Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.8607155Z auth = Auth.Token(github_token) 2025-11-03T15:43:06.8607671Z return Github(auth=auth) 2025-11-03T15:43:06.8607970Z 2025-11-03T15:43:06.8607977Z 2025-11-03T15:43:06.8608437Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.8609239Z repo = gh.get_repo(repo) 2025-11-03T15:43:06.8609749Z return repo.get_issue(number=issue_num) 2025-11-03T15:43:06.8610116Z 2025-11-03T15:43:06.8610124Z 2025-11-03T15:43:06.8610316Z def get_potential_pr_author( 2025-11-03T15:43:06.8611174Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-11-03T15:43:06.8611864Z ) -> str: 2025-11-03T15:43:06.8612391Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-11-03T15:43:06.8613198Z # Fetch the actual username from the original PR. The PR number is 2025-11-03T15:43:06.8613934Z # embedded in the tag name: ciflow// 2025-11-03T15:43:06.8614349Z 2025-11-03T15:43:06.8614551Z gh = get_gh_client(github_token) 2025-11-03T15:43:06.8614885Z 2025-11-03T15:43:06.8615153Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-11-03T15:43:06.8615775Z split_tag = ref_name.split("/") 2025-11-03T15:43:06.8616276Z if ( 2025-11-03T15:43:06.8616666Z len(split_tag) == 3 2025-11-03T15:43:06.8617144Z and split_tag[0] == "ciflow" 2025-11-03T15:43:06.8617669Z and split_tag[2].isnumeric() 2025-11-03T15:43:06.8618161Z ): 2025-11-03T15:43:06.8618545Z pr_number = split_tag[2] 2025-11-03T15:43:06.8619178Z try: 2025-11-03T15:43:06.8619606Z repository = gh.get_repo(repo) 2025-11-03T15:43:06.8620223Z pull = repository.get_pull(number=int(pr_number)) 2025-11-03T15:43:06.8621003Z except Exception as e: 2025-11-03T15:43:06.8621535Z raise Exception( # noqa: TRY002 2025-11-03T15:43:06.8622199Z f"issue with pull request {pr_number} from repo {repository}" 2025-11-03T15:43:06.8623066Z ) from e 2025-11-03T15:43:06.8623643Z return pull.user.login # type: ignore[no-any-return] 2025-11-03T15:43:06.8624349Z # In all other cases, return the original input username 2025-11-03T15:43:06.8624940Z return username 2025-11-03T15:43:06.8625183Z 2025-11-03T15:43:06.8625190Z 2025-11-03T15:43:06.8625422Z def is_exception_branch(branch: str) -> bool: 2025-11-03T15:43:06.8625962Z """ 2025-11-03T15:43:06.8626599Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-11-03T15:43:06.8627382Z """ 2025-11-03T15:43:06.8627933Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-11-03T15:43:06.8628456Z 2025-11-03T15:43:06.8628464Z 2025-11-03T15:43:06.8628663Z def load_yaml(yaml_text: str) -> Any: 2025-11-03T15:43:06.8629158Z try: 2025-11-03T15:43:06.8629543Z data = yaml.safe_load(yaml_text) 2025-11-03T15:43:06.8630055Z return data 2025-11-03T15:43:06.8630468Z except yaml.YAMLError: 2025-11-03T15:43:06.8631238Z log.exception("Error loading YAML") 2025-11-03T15:43:06.8631753Z raise 2025-11-03T15:43:06.8631972Z 2025-11-03T15:43:06.8631979Z 2025-11-03T15:43:06.8632401Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-11-03T15:43:06.8633152Z """ 2025-11-03T15:43:06.8633770Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-11-03T15:43:06.8634372Z 2025-11-03T15:43:06.8634882Z If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.8635648Z and the text below is the list of opted in users. 2025-11-03T15:43:06.8636064Z 2025-11-03T15:43:06.8636437Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-11-03T15:43:06.8637144Z """ 2025-11-03T15:43:06.8637586Z rollout_state_parts = rollout_state.split("---") 2025-11-03T15:43:06.8638218Z if len(rollout_state_parts) >= 2: 2025-11-03T15:43:06.8638822Z return rollout_state_parts[0], rollout_state_parts[1] 2025-11-03T15:43:06.8639420Z else: 2025-11-03T15:43:06.8639798Z return "", rollout_state 2025-11-03T15:43:06.8640115Z 2025-11-03T15:43:06.8640123Z 2025-11-03T15:43:06.8640327Z class UserOptins(dict[str, list[str]]): 2025-11-03T15:43:06.8641037Z """ 2025-11-03T15:43:06.8641574Z Dictionary of users with a list of features they have opted into 2025-11-03T15:43:06.8642231Z """ 2025-11-03T15:43:06.8642445Z 2025-11-03T15:43:06.8642452Z 2025-11-03T15:43:06.8642798Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-11-03T15:43:06.8643453Z """ 2025-11-03T15:43:06.8644159Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-11-03T15:43:06.8644849Z 2025-11-03T15:43:06.8645474Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-11-03T15:43:06.8646487Z - Example line: "@User1,lf,split_build" 2025-11-03T15:43:06.8647171Z - A "#" prefix indicates the user is opted out of all experiments 2025-11-03T15:43:06.8647650Z 2025-11-03T15:43:06.8647663Z 2025-11-03T15:43:06.8647820Z """ 2025-11-03T15:43:06.8648195Z optins = UserOptins() 2025-11-03T15:43:06.8648696Z for user in user_optin_text.split("\n"): 2025-11-03T15:43:06.8649246Z user = user.strip("\r\n\t -") 2025-11-03T15:43:06.8649806Z if not user or not user.startswith("@"): 2025-11-03T15:43:06.8650515Z # Not a valid user. Skip 2025-11-03T15:43:06.8651201Z continue 2025-11-03T15:43:06.8651457Z 2025-11-03T15:43:06.8651630Z if user: 2025-11-03T15:43:06.8652066Z usr_name = user.split(",")[0].strip("@") 2025-11-03T15:43:06.8652774Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-11-03T15:43:06.8653270Z 2025-11-03T15:43:06.8653438Z return optins 2025-11-03T15:43:06.8653683Z 2025-11-03T15:43:06.8653691Z 2025-11-03T15:43:06.8653985Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-11-03T15:43:06.8654587Z """ 2025-11-03T15:43:06.8654982Z Check if the experiment name is valid. 2025-11-03T15:43:06.8655512Z A valid name: 2025-11-03T15:43:06.8656144Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-11-03T15:43:06.8657117Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-11-03T15:43:06.8657849Z - Cannot contain spaces 2025-11-03T15:43:06.8658320Z """ 2025-11-03T15:43:06.8658516Z 2025-11-03T15:43:06.8658785Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-11-03T15:43:06.8659491Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-11-03T15:43:06.8659935Z 2025-11-03T15:43:06.8660103Z if valid: 2025-11-03T15:43:06.8660482Z return True 2025-11-03T15:43:06.8660882Z 2025-11-03T15:43:06.8661059Z log.error( 2025-11-03T15:43:06.8662540Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-11-03T15:43:06.8664111Z ) 2025-11-03T15:43:06.8664472Z return False 2025-11-03T15:43:06.8664709Z 2025-11-03T15:43:06.8664717Z 2025-11-03T15:43:06.8665021Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-11-03T15:43:06.8665659Z """ 2025-11-03T15:43:06.8666378Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-11-03T15:43:06.8667149Z """ 2025-11-03T15:43:06.8667501Z try: 2025-11-03T15:43:06.8667881Z if settings_text: 2025-11-03T15:43:06.8668621Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-11-03T15:43:06.8669416Z # for easy reading 2025-11-03T15:43:06.8670220Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-11-03T15:43:06.8671408Z # the backtick character in shell commands. 2025-11-03T15:43:06.8672035Z backtick = chr(96) # backtick character 2025-11-03T15:43:06.8672704Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-11-03T15:43:06.8673389Z settings = load_yaml(settings_text) 2025-11-03T15:43:06.8673773Z 2025-11-03T15:43:06.8674208Z # For now we just load experiments. We can expand this if/when we add more settings 2025-11-03T15:43:06.8674977Z experiments = {} 2025-11-03T15:43:06.8675277Z 2025-11-03T15:43:06.8675676Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-11-03T15:43:06.8676460Z if not is_valid_experiment_name(exp_name): 2025-11-03T15:43:06.8677597Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-11-03T15:43:06.8678664Z continue 2025-11-03T15:43:06.8678955Z 2025-11-03T15:43:06.8679141Z valid_settings = {} 2025-11-03T15:43:06.8679678Z for setting in exp_settings: 2025-11-03T15:43:06.8680253Z if setting not in Experiment._fields: 2025-11-03T15:43:06.8681075Z log.warning( 2025-11-03T15:43:06.8681811Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-11-03T15:43:06.8682710Z ) 2025-11-03T15:43:06.8683142Z else: 2025-11-03T15:43:06.8683665Z valid_settings[setting] = exp_settings[setting] 2025-11-03T15:43:06.8684099Z 2025-11-03T15:43:06.8684382Z experiments[exp_name] = Experiment(**valid_settings) 2025-11-03T15:43:06.8685015Z return Settings(experiments) 2025-11-03T15:43:06.8685374Z 2025-11-03T15:43:06.8685548Z except Exception: 2025-11-03T15:43:06.8686024Z log.exception("Failed to parse settings") 2025-11-03T15:43:06.8686421Z 2025-11-03T15:43:06.8686588Z return Settings() 2025-11-03T15:43:06.8686842Z 2025-11-03T15:43:06.8686850Z 2025-11-03T15:43:06.8687105Z def parse_settings(rollout_state: str) -> Settings: 2025-11-03T15:43:06.8687683Z """ 2025-11-03T15:43:06.8688126Z Parse settings, if any, from the rollout state. 2025-11-03T15:43:06.8688535Z 2025-11-03T15:43:06.8688890Z If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.8689666Z and the text below is the list of opted in users. 2025-11-03T15:43:06.8690074Z 2025-11-03T15:43:06.8690488Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-11-03T15:43:06.8691452Z """ 2025-11-03T15:43:06.8692024Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.8692790Z return parse_settings_from_text(settings_text) 2025-11-03T15:43:06.8693196Z 2025-11-03T15:43:06.8693204Z 2025-11-03T15:43:06.8693460Z def parse_users(rollout_state: str) -> UserOptins: 2025-11-03T15:43:06.8694018Z """ 2025-11-03T15:43:06.8694415Z Parse users from the rollout state. 2025-11-03T15:43:06.8694766Z 2025-11-03T15:43:06.8694920Z """ 2025-11-03T15:43:06.8695454Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.8696215Z return parse_user_opt_in_from_text(users_text) 2025-11-03T15:43:06.8696632Z 2025-11-03T15:43:06.8696640Z 2025-11-03T15:43:06.8697191Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.8697963Z """ 2025-11-03T15:43:06.8698377Z Check if a user is opted into an experiment 2025-11-03T15:43:06.8698922Z """ 2025-11-03T15:43:06.8699374Z return experiment_name in user_optins.get(user, []) 2025-11-03T15:43:06.8699806Z 2025-11-03T15:43:06.8699814Z 2025-11-03T15:43:06.8700242Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.8701110Z """ 2025-11-03T15:43:06.8701573Z Check if a user explicitly opted out of an experiment 2025-11-03T15:43:06.8702165Z """ 2025-11-03T15:43:06.8702675Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-11-03T15:43:06.8703365Z experiment_optout = "-" + experiment_name 2025-11-03T15:43:06.8704007Z if experiment_optout not in user_optins.get(user, []): 2025-11-03T15:43:06.8704636Z return False 2025-11-03T15:43:06.8704897Z 2025-11-03T15:43:06.8705188Z if is_user_opted_in(user, user_optins, experiment_name): 2025-11-03T15:43:06.8705797Z log.warning( 2025-11-03T15:43:06.8706626Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-11-03T15:43:06.8707515Z ) 2025-11-03T15:43:06.8707733Z 2025-11-03T15:43:06.8707901Z return True 2025-11-03T15:43:06.8708138Z 2025-11-03T15:43:06.8708146Z 2025-11-03T15:43:06.8708327Z def get_runner_prefix( 2025-11-03T15:43:06.8708776Z rollout_state: str, 2025-11-03T15:43:06.8709247Z workflow_requestors: Iterable[str], 2025-11-03T15:43:06.8709769Z branch: str, 2025-11-03T15:43:06.8710267Z eligible_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.8711225Z opt_out_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.8711844Z is_canary: bool = False, 2025-11-03T15:43:06.8712299Z ) -> str: 2025-11-03T15:43:06.8712895Z settings = parse_settings(rollout_state) 2025-11-03T15:43:06.8713487Z user_optins = parse_users(rollout_state) 2025-11-03T15:43:06.8713868Z 2025-11-03T15:43:06.8714046Z fleet_prefix = "" 2025-11-03T15:43:06.8714474Z prefixes = [] 2025-11-03T15:43:06.8715104Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-11-03T15:43:06.8716060Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-11-03T15:43:06.8716782Z log.info( 2025-11-03T15:43:06.8717476Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-11-03T15:43:06.8718246Z ) 2025-11-03T15:43:06.8718633Z continue 2025-11-03T15:43:06.8718882Z 2025-11-03T15:43:06.8719072Z if opt_out_experiments: 2025-11-03T15:43:06.8719616Z if experiment_name in opt_out_experiments: 2025-11-03T15:43:06.8720266Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-11-03T15:43:06.8721385Z log.info( 2025-11-03T15:43:06.8722375Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-11-03T15:43:06.8723361Z ) 2025-11-03T15:43:06.8723762Z continue 2025-11-03T15:43:06.8724033Z 2025-11-03T15:43:06.8724229Z if eligible_experiments: 2025-11-03T15:43:06.8724821Z if experiment_name not in eligible_experiments: 2025-11-03T15:43:06.8725462Z exp_list = ", ".join(eligible_experiments) 2025-11-03T15:43:06.8726022Z log.info( 2025-11-03T15:43:06.8726804Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-11-03T15:43:06.8727650Z ) 2025-11-03T15:43:06.8728057Z continue 2025-11-03T15:43:06.8728535Z elif not experiment_settings.default: 2025-11-03T15:43:06.8729077Z log.info( 2025-11-03T15:43:06.8729875Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-11-03T15:43:06.8730641Z ) 2025-11-03T15:43:06.8731135Z continue 2025-11-03T15:43:06.8731384Z 2025-11-03T15:43:06.8731667Z # Is any workflow_requestor opted out to this experiment? 2025-11-03T15:43:06.8732296Z opted_out_users = [ 2025-11-03T15:43:06.8732742Z requestor 2025-11-03T15:43:06.8733204Z for requestor in workflow_requestors 2025-11-03T15:43:06.8733877Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.8734520Z ] 2025-11-03T15:43:06.8734730Z 2025-11-03T15:43:06.8734917Z if opted_out_users: 2025-11-03T15:43:06.8735368Z log.info( 2025-11-03T15:43:06.8735998Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-11-03T15:43:06.8736704Z ) 2025-11-03T15:43:06.8737086Z continue 2025-11-03T15:43:06.8737346Z 2025-11-03T15:43:06.8737633Z # Is any workflow_requestor opted in to this experiment? 2025-11-03T15:43:06.8738272Z opted_in_users = [ 2025-11-03T15:43:06.8738718Z requestor 2025-11-03T15:43:06.8739184Z for requestor in workflow_requestors 2025-11-03T15:43:06.8739868Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.8740500Z ] 2025-11-03T15:43:06.8740805Z 2025-11-03T15:43:06.8740989Z enabled = False 2025-11-03T15:43:06.8741427Z if opted_in_users: 2025-11-03T15:43:06.8741870Z log.info( 2025-11-03T15:43:06.8742474Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-11-03T15:43:06.8743162Z ) 2025-11-03T15:43:06.8743546Z enabled = True 2025-11-03T15:43:06.8743833Z 2025-11-03T15:43:06.8744055Z elif experiment_settings.rollout_perc: 2025-11-03T15:43:06.8744907Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-11-03T15:43:06.8745991Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-11-03T15:43:06.8746649Z log.info( 2025-11-03T15:43:06.8747519Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-11-03T15:43:06.8748453Z ) 2025-11-03T15:43:06.8748860Z enabled = True 2025-11-03T15:43:06.8749166Z 2025-11-03T15:43:06.8749339Z if enabled: 2025-11-03T15:43:06.8749765Z label = experiment_name 2025-11-03T15:43:06.8750317Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-11-03T15:43:06.8751285Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-11-03T15:43:06.8752173Z # - If it's enabled, then we always list it's prefix first 2025-11-03T15:43:06.8752956Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-11-03T15:43:06.8753630Z if is_canary: 2025-11-03T15:43:06.8754126Z label += CANARY_FLEET_SUFFIX 2025-11-03T15:43:06.8754682Z fleet_prefix = label 2025-11-03T15:43:06.8755178Z else: 2025-11-03T15:43:06.8755613Z prefixes.append(label) 2025-11-03T15:43:06.8755958Z 2025-11-03T15:43:06.8756145Z if len(prefixes) > 1: 2025-11-03T15:43:06.8756596Z log.error( 2025-11-03T15:43:06.8757644Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-11-03T15:43:06.8758782Z ) 2025-11-03T15:43:06.8759169Z prefixes = prefixes[:1] 2025-11-03T15:43:06.8759485Z 2025-11-03T15:43:06.8759676Z # Fleet always comes first 2025-11-03T15:43:06.8760148Z if fleet_prefix: 2025-11-03T15:43:06.8760592Z prefixes.insert(0, fleet_prefix) 2025-11-03T15:43:06.8761057Z 2025-11-03T15:43:06.8761437Z return ".".join(prefixes) + "." if prefixes else "" 2025-11-03T15:43:06.8761871Z 2025-11-03T15:43:06.8761881Z 2025-11-03T15:43:06.8762339Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-11-03T15:43:06.8763134Z """ 2025-11-03T15:43:06.8763719Z Gets the first comment of the issue, which contains the desired rollout state. 2025-11-03T15:43:06.8764300Z 2025-11-03T15:43:06.8764692Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-11-03T15:43:06.8765405Z """ 2025-11-03T15:43:06.8765793Z gh = get_gh_client(github_token) 2025-11-03T15:43:06.8766339Z issue = get_issue(gh, repo, issue_num) 2025-11-03T15:43:06.8766972Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-11-03T15:43:06.8767425Z 2025-11-03T15:43:06.8767433Z 2025-11-03T15:43:06.8767838Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-11-03T15:43:06.8768614Z for _ in range(num_retries): 2025-11-03T15:43:06.8769095Z try: 2025-11-03T15:43:06.8769525Z req = Request(url=url, headers=headers) 2025-11-03T15:43:06.8770192Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-11-03T15:43:06.8770943Z return json.loads(content) 2025-11-03T15:43:06.8771474Z except Exception as e: 2025-11-03T15:43:06.8772019Z log.warning(f"Could not download {url}: {e}") 2025-11-03T15:43:06.8772430Z 2025-11-03T15:43:06.8772814Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-11-03T15:43:06.8773539Z return {} 2025-11-03T15:43:06.8773764Z 2025-11-03T15:43:06.8773771Z 2025-11-03T15:43:06.8773939Z @cache 2025-11-03T15:43:06.8774556Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-11-03T15:43:06.8775320Z """ 2025-11-03T15:43:06.8775709Z Dynamically get PR information 2025-11-03T15:43:06.8776387Z """ 2025-11-03T15:43:06.8776890Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-11-03T15:43:06.8777524Z headers = { 2025-11-03T15:43:06.8777980Z "Accept": "application/vnd.github.v3+json", 2025-11-03T15:43:06.8778589Z "Authorization": f"token {github_token}", 2025-11-03T15:43:06.8779131Z } 2025-11-03T15:43:06.8779555Z json_response: dict[str, Any] = download_json( 2025-11-03T15:43:06.8780164Z url=f"{github_api}/issues/{pr_number}", 2025-11-03T15:43:06.8780817Z headers=headers, 2025-11-03T15:43:06.8781257Z ) 2025-11-03T15:43:06.8781457Z 2025-11-03T15:43:06.8781646Z if not json_response: 2025-11-03T15:43:06.8782215Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-11-03T15:43:06.8782833Z return {} 2025-11-03T15:43:06.8783076Z 2025-11-03T15:43:06.8783260Z return json_response 2025-11-03T15:43:06.8783540Z 2025-11-03T15:43:06.8783548Z 2025-11-03T15:43:06.8783956Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-11-03T15:43:06.8784693Z """ 2025-11-03T15:43:06.8785219Z Dynamically get the latest list of labels from the pull request 2025-11-03T15:43:06.8785882Z """ 2025-11-03T15:43:06.8786366Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-11-03T15:43:06.8786983Z return { 2025-11-03T15:43:06.8787585Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-11-03T15:43:06.8788299Z } 2025-11-03T15:43:06.8788498Z 2025-11-03T15:43:06.8788506Z 2025-11-03T15:43:06.8788680Z def main() -> None: 2025-11-03T15:43:06.8789107Z args = parse_args() 2025-11-03T15:43:06.8789379Z 2025-11-03T15:43:06.8789602Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-11-03T15:43:06.8789999Z 2025-11-03T15:43:06.8790191Z # Check if the PR is opt-out 2025-11-03T15:43:06.8790775Z if args.pr_number: 2025-11-03T15:43:06.8791446Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-11-03T15:43:06.8792342Z if OPT_OUT_LABEL in labels: 2025-11-03T15:43:06.8792852Z log.info( 2025-11-03T15:43:06.8793549Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-11-03T15:43:06.8794312Z ) 2025-11-03T15:43:06.8794868Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.8795542Z sys.exit() 2025-11-03T15:43:06.8795810Z 2025-11-03T15:43:06.8795972Z try: 2025-11-03T15:43:06.8796407Z rollout_state = get_rollout_state_from_issue( 2025-11-03T15:43:06.8797127Z args.github_token, args.github_issue_repo, args.github_issue 2025-11-03T15:43:06.8797769Z ) 2025-11-03T15:43:06.8797975Z 2025-11-03T15:43:06.8798187Z username = get_potential_pr_author( 2025-11-03T15:43:06.8798735Z args.github_token, 2025-11-03T15:43:06.8799215Z args.github_repo, 2025-11-03T15:43:06.8799699Z args.github_actor, 2025-11-03T15:43:06.8800180Z args.github_ref_type, 2025-11-03T15:43:06.8800783Z args.github_branch, 2025-11-03T15:43:06.8801257Z ) 2025-11-03T15:43:06.8801469Z 2025-11-03T15:43:06.8801756Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-11-03T15:43:06.8802214Z 2025-11-03T15:43:06.8802438Z runner_label_prefix = get_runner_prefix( 2025-11-03T15:43:06.8802993Z rollout_state, 2025-11-03T15:43:06.8803489Z (args.github_issue_owner, username), 2025-11-03T15:43:06.8804044Z args.github_branch, 2025-11-03T15:43:06.8804540Z args.eligible_experiments, 2025-11-03T15:43:06.8805081Z args.opt_out_experiments, 2025-11-03T15:43:06.8805589Z is_canary, 2025-11-03T15:43:06.8805999Z ) 2025-11-03T15:43:06.8806211Z 2025-11-03T15:43:06.8806397Z except Exception as e: 2025-11-03T15:43:06.8806852Z log.error( 2025-11-03T15:43:06.8807523Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-11-03T15:43:06.8808426Z ) 2025-11-03T15:43:06.8808644Z 2025-11-03T15:43:06.8808978Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.8809485Z 2025-11-03T15:43:06.8809493Z 2025-11-03T15:43:06.8809674Z if __name__ == "__main__": 2025-11-03T15:43:06.8810125Z main() 2025-11-03T15:43:06.8810343Z 2025-11-03T15:43:06.8903573Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-11-03T15:43:06.8904466Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-11-03T15:43:06.8937509Z shell: /usr/bin/bash -e {0} 2025-11-03T15:43:06.8937988Z env: 2025-11-03T15:43:06.8938618Z GITHUB_TOKEN: *** 2025-11-03T15:43:06.8939033Z ISSUE_NUMBER: 5132 2025-11-03T15:43:06.8939471Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:43:06.8939974Z ISSUE_OWNER: 2025-11-03T15:43:06.8940381Z CHECK_EXPERIMENTS: 2025-11-03T15:43:06.8940934Z OPT_OUT_EXPERIMENTS: lf 2025-11-03T15:43:06.8941391Z PR_NUMBER: 2025-11-03T15:43:06.8941761Z ##[endgroup] 2025-11-03T15:43:08.0551127Z Defaulting to user installation because normal site-packages is not writeable 2025-11-03T15:43:08.8429713Z Collecting urllib3==1.26.18 2025-11-03T15:43:08.8771559Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-11-03T15:43:08.8984659Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.0 MB/s eta 0:00:00 2025-11-03T15:43:08.9199265Z Collecting PyGithub==2.3.0 2025-11-03T15:43:08.9238023Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-11-03T15:43:08.9715480Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-11-03T15:43:08.9749482Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-11-03T15:43:08.9792692Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-11-03T15:43:08.9809007Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-11-03T15:43:08.9823557Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-11-03T15:43:09.0106613Z Collecting Deprecated (from PyGithub==2.3.0) 2025-11-03T15:43:09.0140151Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-11-03T15:43:09.0446873Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-11-03T15:43:09.1672613Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-11-03T15:43:09.1716074Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-11-03T15:43:09.3156475Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-11-03T15:43:09.3223248Z Downloading wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (8.8 kB) 2025-11-03T15:43:09.3424416Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-11-03T15:43:09.3464093Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-11-03T15:43:09.3742562Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-11-03T15:43:09.3814360Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 25.5 MB/s eta 0:00:00 2025-11-03T15:43:09.3859539Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-11-03T15:43:09.3959782Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 44.3 MB/s eta 0:00:00 2025-11-03T15:43:09.3996526Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-11-03T15:43:09.4110330Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 141.5 MB/s eta 0:00:00 2025-11-03T15:43:09.4184980Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-11-03T15:43:09.4253397Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-11-03T15:43:09.4299477Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 67.1 MB/s eta 0:00:00 2025-11-03T15:43:09.4333249Z Downloading wrapt-2.0.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (121 kB) 2025-11-03T15:43:09.4378506Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.4/121.4 kB 38.9 MB/s eta 0:00:00 2025-11-03T15:43:09.4412772Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-11-03T15:43:09.4453789Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 42.5 MB/s eta 0:00:00 2025-11-03T15:43:09.7356356Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-11-03T15:43:10.2753670Z Successfully installed Deprecated-1.3.1 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.0 urllib3-1.26.18 wrapt-2.0.0 2025-11-03T15:43:10.3597060Z ##[group]Run curr_branch="main" 2025-11-03T15:43:10.3597380Z curr_branch="main" 2025-11-03T15:43:10.3597603Z curr_ref_type="branch" 2025-11-03T15:43:10.3597853Z echo "Current branch is '$curr_branch'" 2025-11-03T15:43:10.3598159Z  2025-11-03T15:43:10.3598343Z python3 runner_determinator.py \ 2025-11-03T15:43:10.3598629Z  --github-token "$GITHUB_TOKEN" \ 2025-11-03T15:43:10.3598922Z  --github-issue "$ISSUE_NUMBER" \ 2025-11-03T15:43:10.3599183Z  --github-branch "$curr_branch" \ 2025-11-03T15:43:10.3599463Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-11-03T15:43:10.3599759Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-11-03T15:43:10.3600053Z  --github-ref-type "$curr_ref_type" \ 2025-11-03T15:43:10.3600323Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-11-03T15:43:10.3600621Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-11-03T15:43:10.3601264Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-11-03T15:43:10.3601561Z  --pr-number "${PR_NUMBER}" 2025-11-03T15:43:10.3635765Z shell: /usr/bin/bash -e {0} 2025-11-03T15:43:10.3635995Z env: 2025-11-03T15:43:10.3636582Z GITHUB_TOKEN: *** 2025-11-03T15:43:10.3636772Z ISSUE_NUMBER: 5132 2025-11-03T15:43:10.3636981Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:43:10.3637204Z ISSUE_OWNER: 2025-11-03T15:43:10.3637380Z CHECK_EXPERIMENTS: 2025-11-03T15:43:10.3637566Z OPT_OUT_EXPERIMENTS: lf 2025-11-03T15:43:10.3637760Z PR_NUMBER: 2025-11-03T15:43:10.3637919Z ##[endgroup] 2025-11-03T15:43:10.3690085Z Current branch is 'main' 2025-11-03T15:43:11.7575361Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-11-03T15:43:11.7576175Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-11-03T15:43:11.7577059Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-11-03T15:43:11.7577720Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-11-03T15:43:11.7578247Z INFO : Setting output: label-type='' 2025-11-03T15:43:11.7897249Z Evaluate and set job outputs 2025-11-03T15:43:11.7903892Z Cleaning up orphan processes