2025-11-03T15:43:06.1727336Z Current runner version: '2.329.0' 2025-11-03T15:43:06.1761595Z ##[group]Runner Image Provisioner 2025-11-03T15:43:06.1762960Z Hosted Compute Agent 2025-11-03T15:43:06.1764029Z Version: 20251016.436 2025-11-03T15:43:06.1765200Z Commit: 8ab8ac8bfd662a3739dab9fe09456aba92132568 2025-11-03T15:43:06.1766402Z Build Date: 2025-10-15T20:44:12Z 2025-11-03T15:43:06.1767454Z ##[endgroup] 2025-11-03T15:43:06.1768277Z ##[group]Operating System 2025-11-03T15:43:06.1769469Z Ubuntu 2025-11-03T15:43:06.1770256Z 24.04.3 2025-11-03T15:43:06.1771181Z LTS 2025-11-03T15:43:06.1772023Z ##[endgroup] 2025-11-03T15:43:06.1772851Z ##[group]Runner Image 2025-11-03T15:43:06.1774655Z Image: ubuntu-24.04 2025-11-03T15:43:06.1775582Z Version: 20251030.96.2 2025-11-03T15:43:06.1777431Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251030.96/images/ubuntu/Ubuntu2404-Readme.md 2025-11-03T15:43:06.1780332Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251030.96 2025-11-03T15:43:06.1782324Z ##[endgroup] 2025-11-03T15:43:06.1784240Z ##[group]GITHUB_TOKEN Permissions 2025-11-03T15:43:06.1787128Z Contents: read 2025-11-03T15:43:06.1788222Z Metadata: read 2025-11-03T15:43:06.1789044Z ##[endgroup] 2025-11-03T15:43:06.1792487Z Secret source: Actions 2025-11-03T15:43:06.1794200Z Prepare workflow directory 2025-11-03T15:43:06.2543446Z Prepare all required actions 2025-11-03T15:43:06.2627497Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (3f6538febd727b782e6e13cfd026a309fb14351d) 2025-11-03T15:43:06.2635596Z ##[group] Inputs 2025-11-03T15:43:06.2636632Z check_experiments: 2025-11-03T15:43:06.2637598Z opt_out_experiments: 2025-11-03T15:43:06.2638493Z triggering_actor: pytorchmergebot 2025-11-03T15:43:06.2639610Z issue_owner: 2025-11-03T15:43:06.2640366Z curr_branch: main 2025-11-03T15:43:06.2641285Z curr_ref_type: branch 2025-11-03T15:43:06.2642541Z issue_number: 5132 2025-11-03T15:43:06.2643665Z ##[endgroup] 2025-11-03T15:43:06.2644854Z Complete job name: before-test / get-label-type / runner-determinator 2025-11-03T15:43:06.7504199Z ##[group]Run cat < runner_determinator.py 2025-11-03T15:43:06.7506658Z cat < runner_determinator.py 2025-11-03T15:43:06.7507409Z # flake8: noqa: G004 2025-11-03T15:43:06.7507988Z  2025-11-03T15:43:06.7508792Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-11-03T15:43:06.7509971Z # must be kept in sync. You can do it easily by running the following command: 2025-11-03T15:43:06.7510973Z # python .github/scripts/update_runner_determinator.py 2025-11-03T15:43:06.7511688Z  2025-11-03T15:43:06.7512208Z """ 2025-11-03T15:43:06.7512969Z This runner determinator is used to determine which set of runners to run a 2025-11-03T15:43:06.7514797Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-11-03T15:43:06.7516169Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-11-03T15:43:06.7517185Z of which runners should be used to run which job. 2025-11-03T15:43:06.7517941Z  2025-11-03T15:43:06.7518669Z The configuration has two parts, the settings and a list of opted-in users, 2025-11-03T15:43:06.7519777Z separated by a line containing "---". If the line is not present, the 2025-11-03T15:43:06.7520886Z settings are considered to be empty with only the second part, the user 2025-11-03T15:43:06.7521737Z list, defined. 2025-11-03T15:43:06.7522390Z  2025-11-03T15:43:06.7523287Z The first part is a YAML block that defines the rollout settings. This can be 2025-11-03T15:43:06.7524599Z used to define any settings that are needed to determine which runners to use. 2025-11-03T15:43:06.7525610Z It's fields are defined by the RolloutSettings class below. 2025-11-03T15:43:06.7526358Z  2025-11-03T15:43:06.7527455Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-11-03T15:43:06.7528490Z The user list is also a comma separated list of additional features or 2025-11-03T15:43:06.7529404Z experiments which the user could be opted in to. 2025-11-03T15:43:06.7530150Z  2025-11-03T15:43:06.7530676Z The user list has the following rules: 2025-11-03T15:43:06.7531307Z  2025-11-03T15:43:06.7532068Z - Users are GitHub usernames, which must start with the @ prefix 2025-11-03T15:43:06.7533331Z - Each user is also a comma-separated list of features/experiments to enable 2025-11-03T15:43:06.7534307Z - A "#" prefix opts the user out of all experiments 2025-11-03T15:43:06.7535071Z  2025-11-03T15:43:06.7535523Z Example config: 2025-11-03T15:43:06.7536169Z  # A list of experiments that can be opted into. 2025-11-03T15:43:06.7611903Z  # This defines the behavior they'll induce when opted into. 2025-11-03T15:43:06.7612701Z  # Expected syntax is: 2025-11-03T15:43:06.7613832Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-11-03T15:43:06.7614928Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-11-03T15:43:06.7615736Z  2025-11-03T15:43:06.7616169Z  experiments: 2025-11-03T15:43:06.7616632Z  lf: 2025-11-03T15:43:06.7617069Z  rollout_percent: 25 2025-11-03T15:43:06.7617600Z  all_branches: false 2025-11-03T15:43:06.7618132Z  default: true 2025-11-03T15:43:06.7618607Z  --- 2025-11-03T15:43:06.7618995Z  2025-11-03T15:43:06.7619382Z  # Opt-ins: 2025-11-03T15:43:06.7620060Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-11-03T15:43:06.7621317Z  # and specifying experiments to enable in a comma-separated list. 2025-11-03T15:43:06.7622209Z  # To always opt out of an experiment, prefix it with a "-". 2025-11-03T15:43:06.7622968Z  # Experiments should be from the above list. 2025-11-03T15:43:06.7623701Z  2025-11-03T15:43:06.7624112Z  @User1,-lf,split_build 2025-11-03T15:43:06.7624646Z  @User2,lf 2025-11-03T15:43:06.7625109Z  @User3,split_build 2025-11-03T15:43:06.7625598Z """ 2025-11-03T15:43:06.7625985Z  2025-11-03T15:43:06.7626374Z import json 2025-11-03T15:43:06.7626811Z import logging 2025-11-03T15:43:06.7627262Z import os 2025-11-03T15:43:06.7627692Z import random 2025-11-03T15:43:06.7628138Z import re 2025-11-03T15:43:06.7628555Z import sys 2025-11-03T15:43:06.7629032Z from argparse import ArgumentParser 2025-11-03T15:43:06.7629718Z from collections.abc import Iterable 2025-11-03T15:43:06.7630329Z from functools import cache 2025-11-03T15:43:06.7630881Z from logging import LogRecord 2025-11-03T15:43:06.7631454Z from typing import Any, NamedTuple 2025-11-03T15:43:06.7632079Z from urllib.request import Request, urlopen 2025-11-03T15:43:06.7632684Z  2025-11-03T15:43:06.7633172Z import yaml 2025-11-03T15:43:06.7633688Z from github import Auth, Github 2025-11-03T15:43:06.7634264Z from github.Issue import Issue 2025-11-03T15:43:06.7634786Z  2025-11-03T15:43:06.7635157Z  2025-11-03T15:43:06.7635624Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-11-03T15:43:06.7636421Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-11-03T15:43:06.7637417Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-11-03T15:43:06.7638199Z  2025-11-03T15:43:06.7638864Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-11-03T15:43:06.7639536Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-11-03T15:43:06.7640148Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-11-03T15:43:06.7640817Z OPT_OUT_LABEL = "no-runner-experiments" 2025-11-03T15:43:06.7641395Z  2025-11-03T15:43:06.7641825Z SETTING_EXPERIMENTS = "experiments" 2025-11-03T15:43:06.7642374Z  2025-11-03T15:43:06.7642776Z LF_FLEET_EXPERIMENT = "lf" 2025-11-03T15:43:06.7643622Z CANARY_FLEET_SUFFIX = ".c" 2025-11-03T15:43:06.7644137Z  2025-11-03T15:43:06.7644514Z  2025-11-03T15:43:06.7644935Z class Experiment(NamedTuple): 2025-11-03T15:43:06.7645491Z  rollout_perc: float = ( 2025-11-03T15:43:06.7646248Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-11-03T15:43:06.7647002Z  ) 2025-11-03T15:43:06.7647433Z  all_branches: bool = ( 2025-11-03T15:43:06.7648179Z  False # If True, the experiment is also enabled on the exception branches 2025-11-03T15:43:06.7648917Z  ) 2025-11-03T15:43:06.7649358Z  default: bool = ( 2025-11-03T15:43:06.7650032Z  True # If True, the experiment is enabled by default for all queries 2025-11-03T15:43:06.7650737Z  ) 2025-11-03T15:43:06.7651127Z  2025-11-03T15:43:06.7651541Z  # Add more fields as needed 2025-11-03T15:43:06.7652058Z  2025-11-03T15:43:06.7652434Z  2025-11-03T15:43:06.7652858Z class Settings(NamedTuple): 2025-11-03T15:43:06.7653611Z  """ 2025-11-03T15:43:06.7654154Z  Settings for the experiments that can be opted into. 2025-11-03T15:43:06.7654802Z  """ 2025-11-03T15:43:06.7655207Z  2025-11-03T15:43:06.7655658Z  experiments: dict[str, Experiment] = {} 2025-11-03T15:43:06.7656252Z  2025-11-03T15:43:06.7656774Z  2025-11-03T15:43:06.7657239Z class ColorFormatter(logging.Formatter): 2025-11-03T15:43:06.7657953Z  """Color codes the log messages based on the log level""" 2025-11-03T15:43:06.7658601Z  2025-11-03T15:43:06.7658978Z  COLORS = { 2025-11-03T15:43:06.7659469Z  "WARNING": "\033[33m", # Yellow 2025-11-03T15:43:06.7660054Z  "ERROR": "\033[31m", # Red 2025-11-03T15:43:06.7660616Z  "CRITICAL": "\033[31m", # Red 2025-11-03T15:43:06.7661182Z  "INFO": "\033[0m", # Reset 2025-11-03T15:43:06.7661740Z  "DEBUG": "\033[0m", # Reset 2025-11-03T15:43:06.7662268Z  } 2025-11-03T15:43:06.7662656Z  2025-11-03T15:43:06.7663234Z  def format(self, record: LogRecord) -> str: 2025-11-03T15:43:06.7664075Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-11-03T15:43:06.7664937Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-11-03T15:43:06.7665589Z  return super().format(record) 2025-11-03T15:43:06.7666125Z  2025-11-03T15:43:06.7666489Z  2025-11-03T15:43:06.7666918Z handler = logging.StreamHandler() 2025-11-03T15:43:06.7667758Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-11-03T15:43:06.7668545Z  2025-11-03T15:43:06.7669042Z log = logging.getLogger(os.path.basename(__file__)) 2025-11-03T15:43:06.7669715Z log.addHandler(handler) 2025-11-03T15:43:06.7670240Z log.setLevel(logging.INFO) 2025-11-03T15:43:06.7670754Z  2025-11-03T15:43:06.7671124Z  2025-11-03T15:43:06.7671634Z def set_github_output(key: str, value: str) -> None: 2025-11-03T15:43:06.7672260Z  """ 2025-11-03T15:43:06.7672847Z  Defines outputs of the github action that invokes this script 2025-11-03T15:43:06.7674259Z  """ 2025-11-03T15:43:06.7674704Z  if not GITHUB_OUTPUT: 2025-11-03T15:43:06.7675896Z  # 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.7677118Z  log.warning( 2025-11-03T15:43:06.7678087Z  "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.7679086Z  ) 2025-11-03T15:43:06.7679588Z  print(f"::set-output name={key}::{value}") 2025-11-03T15:43:06.7680191Z  return 2025-11-03T15:43:06.7680620Z  2025-11-03T15:43:06.7681057Z  with open(GITHUB_OUTPUT, "a") as f: 2025-11-03T15:43:06.7681707Z  log.info(f"Setting output: {key}='{value}'") 2025-11-03T15:43:06.7682358Z  f.write(f"{key}={value}\n") 2025-11-03T15:43:06.7682901Z  2025-11-03T15:43:06.7683554Z  2025-11-03T15:43:06.7684132Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-11-03T15:43:06.7684853Z  return frozenset( 2025-11-03T15:43:06.7685570Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-11-03T15:43:06.7686308Z  ) 2025-11-03T15:43:06.7686723Z  2025-11-03T15:43:06.7687102Z  2025-11-03T15:43:06.7687517Z def parse_args() -> Any: 2025-11-03T15:43:06.7688192Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-11-03T15:43:06.7689160Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-11-03T15:43:06.7690008Z  parser.add_argument( 2025-11-03T15:43:06.7690548Z  "--github-issue-repo", 2025-11-03T15:43:06.7691102Z  type=str, 2025-11-03T15:43:06.7691594Z  required=False, 2025-11-03T15:43:06.7692307Z  default="pytorch/test-infra", 2025-11-03T15:43:06.7692940Z  help="GitHub repo to get the issue", 2025-11-03T15:43:06.7693970Z  ) 2025-11-03T15:43:06.7694407Z  parser.add_argument( 2025-11-03T15:43:06.7694936Z  "--github-repo", 2025-11-03T15:43:06.7695446Z  type=str, 2025-11-03T15:43:06.7695932Z  required=True, 2025-11-03T15:43:06.7696500Z  help="GitHub repo where CI is running", 2025-11-03T15:43:06.7697073Z  ) 2025-11-03T15:43:06.7697503Z  parser.add_argument( 2025-11-03T15:43:06.7698214Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-11-03T15:43:06.7698928Z  ) 2025-11-03T15:43:06.7699351Z  parser.add_argument( 2025-11-03T15:43:06.7700074Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-11-03T15:43:06.7700836Z  ) 2025-11-03T15:43:06.7701262Z  parser.add_argument( 2025-11-03T15:43:06.7702015Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-11-03T15:43:06.7702764Z  ) 2025-11-03T15:43:06.7703314Z  parser.add_argument( 2025-11-03T15:43:06.7704082Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-11-03T15:43:06.7704853Z  ) 2025-11-03T15:43:06.7705313Z  parser.add_argument( 2025-11-03T15:43:06.7705852Z  "--github-ref-type", 2025-11-03T15:43:06.7706392Z  type=str, 2025-11-03T15:43:06.7706881Z  required=True, 2025-11-03T15:43:06.7707496Z  help="Current GitHub ref type, branch or tag", 2025-11-03T15:43:06.7708108Z  ) 2025-11-03T15:43:06.7708539Z  parser.add_argument( 2025-11-03T15:43:06.7709241Z  "--eligible-experiments", 2025-11-03T15:43:06.7709844Z  type=_str_comma_separated_to_set, 2025-11-03T15:43:06.7710433Z  required=False, 2025-11-03T15:43:06.7710946Z  default="", 2025-11-03T15:43:06.7711902Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-11-03T15:43:06.7712905Z  ) 2025-11-03T15:43:06.7713456Z  parser.add_argument( 2025-11-03T15:43:06.7713994Z  "--opt-out-experiments", 2025-11-03T15:43:06.7714589Z  type=_str_comma_separated_to_set, 2025-11-03T15:43:06.7715168Z  required=False, 2025-11-03T15:43:06.7715679Z  default="", 2025-11-03T15:43:06.7716157Z  help=( 2025-11-03T15:43:06.7716931Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-11-03T15:43:06.7718170Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-11-03T15:43:06.7719075Z  ), 2025-11-03T15:43:06.7719502Z  ) 2025-11-03T15:43:06.7719935Z  parser.add_argument( 2025-11-03T15:43:06.7720461Z  "--pr-number", 2025-11-03T15:43:06.7720966Z  type=str, 2025-11-03T15:43:06.7721455Z  required=False, 2025-11-03T15:43:06.7721966Z  default="", 2025-11-03T15:43:06.7722540Z  help="the optional PR number where this is run", 2025-11-03T15:43:06.7723433Z  ) 2025-11-03T15:43:06.7723850Z  2025-11-03T15:43:06.7724288Z  return parser.parse_args() 2025-11-03T15:43:06.7724841Z  2025-11-03T15:43:06.7725230Z  2025-11-03T15:43:06.7725896Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.7726904Z  auth = Auth.Token(github_token) 2025-11-03T15:43:06.7727533Z  return Github(auth=auth) 2025-11-03T15:43:06.7728057Z  2025-11-03T15:43:06.7728432Z  2025-11-03T15:43:06.7729160Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.7730052Z  repo = gh.get_repo(repo) 2025-11-03T15:43:06.7730648Z  return repo.get_issue(number=issue_num) 2025-11-03T15:43:06.7731235Z  2025-11-03T15:43:06.7731624Z  2025-11-03T15:43:06.7732050Z def get_potential_pr_author( 2025-11-03T15:43:06.7732813Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-11-03T15:43:06.7733901Z ) -> str: 2025-11-03T15:43:06.7734532Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-11-03T15:43:06.7735433Z  # Fetch the actual username from the original PR. The PR number is 2025-11-03T15:43:06.7736292Z  # embedded in the tag name: ciflow// 2025-11-03T15:43:06.7736931Z  2025-11-03T15:43:06.7737368Z  gh = get_gh_client(github_token) 2025-11-03T15:43:06.7737930Z  2025-11-03T15:43:06.7738453Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-11-03T15:43:06.7739174Z  split_tag = ref_name.split("/") 2025-11-03T15:43:06.7739735Z  if ( 2025-11-03T15:43:06.7740208Z  len(split_tag) == 3 2025-11-03T15:43:06.7740794Z  and split_tag[0] == "ciflow" 2025-11-03T15:43:06.7741407Z  and split_tag[2].isnumeric() 2025-11-03T15:43:06.7741971Z  ): 2025-11-03T15:43:06.7742443Z  pr_number = split_tag[2] 2025-11-03T15:43:06.7743122Z  try: 2025-11-03T15:43:06.7743645Z  repository = gh.get_repo(repo) 2025-11-03T15:43:06.7744517Z  pull = repository.get_pull(number=int(pr_number)) 2025-11-03T15:43:06.7745223Z  except Exception as e: 2025-11-03T15:43:06.7745830Z  raise Exception( # noqa: TRY002 2025-11-03T15:43:06.7746607Z  f"issue with pull request {pr_number} from repo {repository}" 2025-11-03T15:43:06.7747330Z  ) from e 2025-11-03T15:43:06.7747977Z  return pull.user.login # type: ignore[no-any-return] 2025-11-03T15:43:06.7748773Z  # In all other cases, return the original input username 2025-11-03T15:43:06.7749444Z  return username 2025-11-03T15:43:06.7749920Z  2025-11-03T15:43:06.7750301Z  2025-11-03T15:43:06.7750778Z def is_exception_branch(branch: str) -> bool: 2025-11-03T15:43:06.7751375Z  """ 2025-11-03T15:43:06.7752294Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-11-03T15:43:06.7753415Z  """ 2025-11-03T15:43:06.7754062Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-11-03T15:43:06.7754798Z  2025-11-03T15:43:06.7755175Z  2025-11-03T15:43:06.7755614Z def load_yaml(yaml_text: str) -> Any: 2025-11-03T15:43:06.7756173Z  try: 2025-11-03T15:43:06.7756656Z  data = yaml.safe_load(yaml_text) 2025-11-03T15:43:06.7757231Z  return data 2025-11-03T15:43:06.7757737Z  except yaml.YAMLError: 2025-11-03T15:43:06.7758319Z  log.exception("Error loading YAML") 2025-11-03T15:43:06.7758905Z  raise 2025-11-03T15:43:06.7759349Z  2025-11-03T15:43:06.7759714Z  2025-11-03T15:43:06.7760407Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-11-03T15:43:06.7761213Z  """ 2025-11-03T15:43:06.7762053Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-11-03T15:43:06.7762869Z  2025-11-03T15:43:06.7763771Z  If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.7764647Z  and the text below is the list of opted in users. 2025-11-03T15:43:06.7765260Z  2025-11-03T15:43:06.7765902Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-11-03T15:43:06.7766672Z  """ 2025-11-03T15:43:06.7767196Z  rollout_state_parts = rollout_state.split("---") 2025-11-03T15:43:06.7767862Z  if len(rollout_state_parts) >= 2: 2025-11-03T15:43:06.7768564Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-11-03T15:43:06.7769216Z  else: 2025-11-03T15:43:06.7769670Z  return "", rollout_state 2025-11-03T15:43:06.7770203Z  2025-11-03T15:43:06.7770583Z  2025-11-03T15:43:06.7771035Z class UserOptins(dict[str, list[str]]): 2025-11-03T15:43:06.7771596Z  """ 2025-11-03T15:43:06.7772198Z  Dictionary of users with a list of features they have opted into 2025-11-03T15:43:06.7772907Z  """ 2025-11-03T15:43:06.7773978Z  2025-11-03T15:43:06.7774358Z  2025-11-03T15:43:06.7774955Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-11-03T15:43:06.7775687Z  """ 2025-11-03T15:43:06.7776482Z  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.7777380Z  2025-11-03T15:43:06.7778251Z  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.7779322Z  - Example line: "@User1,lf,split_build" 2025-11-03T15:43:06.7780253Z  - A "#" prefix indicates the user is opted out of all experiments 2025-11-03T15:43:06.7780952Z  2025-11-03T15:43:06.7781324Z  2025-11-03T15:43:06.7781700Z  """ 2025-11-03T15:43:06.7782130Z  optins = UserOptins() 2025-11-03T15:43:06.7782711Z  for user in user_optin_text.split("\n"): 2025-11-03T15:43:06.7783573Z  user = user.strip("\r\n\t -") 2025-11-03T15:43:06.7784198Z  if not user or not user.startswith("@"): 2025-11-03T15:43:06.7784818Z  # Not a valid user. Skip 2025-11-03T15:43:06.7785382Z  continue 2025-11-03T15:43:06.7785849Z  2025-11-03T15:43:06.7786268Z  if user: 2025-11-03T15:43:06.7786791Z  usr_name = user.split(",")[0].strip("@") 2025-11-03T15:43:06.7787561Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-11-03T15:43:06.7788258Z  2025-11-03T15:43:06.7788665Z  return optins 2025-11-03T15:43:06.7789122Z  2025-11-03T15:43:06.7789481Z  2025-11-03T15:43:06.7790030Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-11-03T15:43:06.7790710Z  """ 2025-11-03T15:43:06.7791181Z  Check if the experiment name is valid. 2025-11-03T15:43:06.7791758Z  A valid name: 2025-11-03T15:43:06.7792504Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-11-03T15:43:06.7793656Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-11-03T15:43:06.7794445Z  - Cannot contain spaces 2025-11-03T15:43:06.7794984Z  """ 2025-11-03T15:43:06.7795382Z  2025-11-03T15:43:06.7795896Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-11-03T15:43:06.7796692Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-11-03T15:43:06.7797676Z  2025-11-03T15:43:06.7798158Z  if valid: 2025-11-03T15:43:06.7798624Z  return True 2025-11-03T15:43:06.7799091Z  2025-11-03T15:43:06.7799475Z  log.error( 2025-11-03T15:43:06.7801076Z  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.7802728Z  ) 2025-11-03T15:43:06.7803254Z  return False 2025-11-03T15:43:06.7803707Z  2025-11-03T15:43:06.7804079Z  2025-11-03T15:43:06.7804647Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-11-03T15:43:06.7805337Z  """ 2025-11-03T15:43:06.7806001Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-11-03T15:43:06.7806781Z  """ 2025-11-03T15:43:06.7807200Z  try: 2025-11-03T15:43:06.7807617Z  if settings_text: 2025-11-03T15:43:06.7808445Z  # 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.7809309Z  # for easy reading 2025-11-03T15:43:06.7810198Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-11-03T15:43:06.7811198Z  # the backtick character in shell commands. 2025-11-03T15:43:06.7811872Z  backtick = chr(96) # backtick character 2025-11-03T15:43:06.7812641Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-11-03T15:43:06.7813488Z  settings = load_yaml(settings_text) 2025-11-03T15:43:06.7814053Z  2025-11-03T15:43:06.7814709Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-11-03T15:43:06.7815659Z  experiments = {} 2025-11-03T15:43:06.7816176Z  2025-11-03T15:43:06.7816792Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-11-03T15:43:06.7817637Z  if not is_valid_experiment_name(exp_name): 2025-11-03T15:43:06.7818842Z  # 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.7819960Z  continue 2025-11-03T15:43:06.7820474Z  2025-11-03T15:43:06.7820886Z  valid_settings = {} 2025-11-03T15:43:06.7821492Z  for setting in exp_settings: 2025-11-03T15:43:06.7822145Z  if setting not in Experiment._fields: 2025-11-03T15:43:06.7822780Z  log.warning( 2025-11-03T15:43:06.7823691Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-11-03T15:43:06.7824473Z  ) 2025-11-03T15:43:06.7824983Z  else: 2025-11-03T15:43:06.7825589Z  valid_settings[setting] = exp_settings[setting] 2025-11-03T15:43:06.7826226Z  2025-11-03T15:43:06.7826763Z  experiments[exp_name] = Experiment(**valid_settings) 2025-11-03T15:43:06.7827481Z  return Settings(experiments) 2025-11-03T15:43:06.7828044Z  2025-11-03T15:43:06.7828438Z  except Exception: 2025-11-03T15:43:06.7829018Z  log.exception("Failed to parse settings") 2025-11-03T15:43:06.7829608Z  2025-11-03T15:43:06.7830011Z  return Settings() 2025-11-03T15:43:06.7830493Z  2025-11-03T15:43:06.7830869Z  2025-11-03T15:43:06.7831507Z def parse_settings(rollout_state: str) -> Settings: 2025-11-03T15:43:06.7832149Z  """ 2025-11-03T15:43:06.7832662Z  Parse settings, if any, from the rollout state. 2025-11-03T15:43:06.7833377Z  2025-11-03T15:43:06.7834009Z  If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.7834854Z  and the text below is the list of opted in users. 2025-11-03T15:43:06.7835470Z  2025-11-03T15:43:06.7836126Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-11-03T15:43:06.7836922Z  """ 2025-11-03T15:43:06.7837554Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.7838395Z  return parse_settings_from_text(settings_text) 2025-11-03T15:43:06.7838997Z  2025-11-03T15:43:06.7839368Z  2025-11-03T15:43:06.7839880Z def parse_users(rollout_state: str) -> UserOptins: 2025-11-03T15:43:06.7840510Z  """ 2025-11-03T15:43:06.7840977Z  Parse users from the rollout state. 2025-11-03T15:43:06.7841544Z  2025-11-03T15:43:06.7841923Z  """ 2025-11-03T15:43:06.7842546Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.7843475Z  return parse_user_opt_in_from_text(users_text) 2025-11-03T15:43:06.7844090Z  2025-11-03T15:43:06.7844460Z  2025-11-03T15:43:06.7845156Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.7845975Z  """ 2025-11-03T15:43:06.7846467Z  Check if a user is opted into an experiment 2025-11-03T15:43:06.7847058Z  """ 2025-11-03T15:43:06.7847590Z  return experiment_name in user_optins.get(user, []) 2025-11-03T15:43:06.7848236Z  2025-11-03T15:43:06.7848738Z  2025-11-03T15:43:06.7849438Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.7850275Z  """ 2025-11-03T15:43:06.7850819Z  Check if a user explicitly opted out of an experiment 2025-11-03T15:43:06.7851468Z  """ 2025-11-03T15:43:06.7852051Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-11-03T15:43:06.7852837Z  experiment_optout = "-" + experiment_name 2025-11-03T15:43:06.7853677Z  if experiment_optout not in user_optins.get(user, []): 2025-11-03T15:43:06.7854357Z  return False 2025-11-03T15:43:06.7854829Z  2025-11-03T15:43:06.7855344Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-11-03T15:43:06.7856015Z  log.warning( 2025-11-03T15:43:06.7856928Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-11-03T15:43:06.7857890Z  ) 2025-11-03T15:43:06.7858308Z  2025-11-03T15:43:06.7858698Z  return True 2025-11-03T15:43:06.7859144Z  2025-11-03T15:43:06.7859522Z  2025-11-03T15:43:06.7859926Z def get_runner_prefix( 2025-11-03T15:43:06.7860455Z  rollout_state: str, 2025-11-03T15:43:06.7861019Z  workflow_requestors: Iterable[str], 2025-11-03T15:43:06.7861595Z  branch: str, 2025-11-03T15:43:06.7862185Z  eligible_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.7862940Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.7863696Z  is_canary: bool = False, 2025-11-03T15:43:06.7864225Z ) -> str: 2025-11-03T15:43:06.7864734Z  settings = parse_settings(rollout_state) 2025-11-03T15:43:06.7865407Z  user_optins = parse_users(rollout_state) 2025-11-03T15:43:06.7865993Z  2025-11-03T15:43:06.7866547Z  fleet_prefix = "" 2025-11-03T15:43:06.7867060Z  prefixes = [] 2025-11-03T15:43:06.7867794Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-11-03T15:43:06.7868836Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-11-03T15:43:06.7869615Z  log.info( 2025-11-03T15:43:06.7870396Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-11-03T15:43:06.7871215Z  ) 2025-11-03T15:43:06.7871688Z  continue 2025-11-03T15:43:06.7872155Z  2025-11-03T15:43:06.7872573Z  if opt_out_experiments: 2025-11-03T15:43:06.7873308Z  if experiment_name in opt_out_experiments: 2025-11-03T15:43:06.7874040Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-11-03T15:43:06.7874709Z  log.info( 2025-11-03T15:43:06.7875749Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-11-03T15:43:06.7876805Z  ) 2025-11-03T15:43:06.7877286Z  continue 2025-11-03T15:43:06.7877779Z  2025-11-03T15:43:06.7878199Z  if eligible_experiments: 2025-11-03T15:43:06.7878845Z  if experiment_name not in eligible_experiments: 2025-11-03T15:43:06.7879559Z  exp_list = ", ".join(eligible_experiments) 2025-11-03T15:43:06.7880172Z  log.info( 2025-11-03T15:43:06.7881050Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-11-03T15:43:06.7881952Z  ) 2025-11-03T15:43:06.7882551Z  continue 2025-11-03T15:43:06.7883233Z  elif not experiment_settings.default: 2025-11-03T15:43:06.7883830Z  log.info( 2025-11-03T15:43:06.7884590Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-11-03T15:43:06.7885378Z  ) 2025-11-03T15:43:06.7885836Z  continue 2025-11-03T15:43:06.7886305Z  2025-11-03T15:43:06.7886833Z  # Is any workflow_requestor opted out to this experiment? 2025-11-03T15:43:06.7887520Z  opted_out_users = [ 2025-11-03T15:43:06.7888057Z  requestor 2025-11-03T15:43:06.7888642Z  for requestor in workflow_requestors 2025-11-03T15:43:06.7889396Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.7890084Z  ] 2025-11-03T15:43:06.7890499Z  2025-11-03T15:43:06.7890908Z  if opted_out_users: 2025-11-03T15:43:06.7891458Z  log.info( 2025-11-03T15:43:06.7892181Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-11-03T15:43:06.7892943Z  ) 2025-11-03T15:43:06.7893495Z  continue 2025-11-03T15:43:06.7893968Z  2025-11-03T15:43:06.7894481Z  # Is any workflow_requestor opted in to this experiment? 2025-11-03T15:43:06.7895154Z  opted_in_users = [ 2025-11-03T15:43:06.7895687Z  requestor 2025-11-03T15:43:06.7896233Z  for requestor in workflow_requestors 2025-11-03T15:43:06.7896984Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.7897663Z  ] 2025-11-03T15:43:06.7898075Z  2025-11-03T15:43:06.7898474Z  enabled = False 2025-11-03T15:43:06.7899002Z  if opted_in_users: 2025-11-03T15:43:06.7899648Z  log.info( 2025-11-03T15:43:06.7900366Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-11-03T15:43:06.7901116Z  ) 2025-11-03T15:43:06.7901567Z  enabled = True 2025-11-03T15:43:06.7902072Z  2025-11-03T15:43:06.7902540Z  elif experiment_settings.rollout_perc: 2025-11-03T15:43:06.7903571Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-11-03T15:43:06.7904614Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-11-03T15:43:06.7905329Z  log.info( 2025-11-03T15:43:06.7906305Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-11-03T15:43:06.7907288Z  ) 2025-11-03T15:43:06.7907787Z  enabled = True 2025-11-03T15:43:06.7908303Z  2025-11-03T15:43:06.7908701Z  if enabled: 2025-11-03T15:43:06.7909213Z  label = experiment_name 2025-11-03T15:43:06.7909851Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-11-03T15:43:06.7911060Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-11-03T15:43:06.7912031Z  # - If it's enabled, then we always list it's prefix first 2025-11-03T15:43:06.7912890Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-11-03T15:43:06.7913734Z  if is_canary: 2025-11-03T15:43:06.7914310Z  label += CANARY_FLEET_SUFFIX 2025-11-03T15:43:06.7914929Z  fleet_prefix = label 2025-11-03T15:43:06.7915481Z  else: 2025-11-03T15:43:06.7916156Z  prefixes.append(label) 2025-11-03T15:43:06.7916714Z  2025-11-03T15:43:06.7917122Z  if len(prefixes) > 1: 2025-11-03T15:43:06.7917636Z  log.error( 2025-11-03T15:43:06.7918961Z  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.7920180Z  ) 2025-11-03T15:43:06.7920636Z  prefixes = prefixes[:1] 2025-11-03T15:43:06.7921314Z  2025-11-03T15:43:06.7921863Z  # Fleet always comes first 2025-11-03T15:43:06.7922418Z  if fleet_prefix: 2025-11-03T15:43:06.7922952Z  prefixes.insert(0, fleet_prefix) 2025-11-03T15:43:06.7923677Z  2025-11-03T15:43:06.7924185Z  return ".".join(prefixes) + "." if prefixes else "" 2025-11-03T15:43:06.7924817Z  2025-11-03T15:43:06.7925208Z  2025-11-03T15:43:06.7925908Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-11-03T15:43:06.7926757Z  """ 2025-11-03T15:43:06.7927410Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-11-03T15:43:06.7928182Z  2025-11-03T15:43:06.7928810Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-11-03T15:43:06.7929574Z  """ 2025-11-03T15:43:06.7930031Z  gh = get_gh_client(github_token) 2025-11-03T15:43:06.7930655Z  issue = get_issue(gh, repo, issue_num) 2025-11-03T15:43:06.7931377Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-11-03T15:43:06.7932025Z  2025-11-03T15:43:06.7932396Z  2025-11-03T15:43:06.7933144Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-11-03T15:43:06.7934169Z  for _ in range(num_retries): 2025-11-03T15:43:06.7934729Z  try: 2025-11-03T15:43:06.7935235Z  req = Request(url=url, headers=headers) 2025-11-03T15:43:06.7935972Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-11-03T15:43:06.7936687Z  return json.loads(content) 2025-11-03T15:43:06.7937284Z  except Exception as e: 2025-11-03T15:43:06.7937924Z  log.warning(f"Could not download {url}: {e}") 2025-11-03T15:43:06.7938534Z  2025-11-03T15:43:06.7939165Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-11-03T15:43:06.7939940Z  return {} 2025-11-03T15:43:06.7940382Z  2025-11-03T15:43:06.7940753Z  2025-11-03T15:43:06.7941132Z @cache 2025-11-03T15:43:06.7941843Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-11-03T15:43:06.7942675Z  """ 2025-11-03T15:43:06.7943239Z  Dynamically get PR information 2025-11-03T15:43:06.7943781Z  """ 2025-11-03T15:43:06.7944358Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-11-03T15:43:06.7945041Z  headers = { 2025-11-03T15:43:06.7945590Z  "Accept": "application/vnd.github.v3+json", 2025-11-03T15:43:06.7946270Z  "Authorization": f"token {github_token}", 2025-11-03T15:43:06.7946858Z  } 2025-11-03T15:43:06.7947365Z  json_response: dict[str, Any] = download_json( 2025-11-03T15:43:06.7948045Z  url=f"{github_api}/issues/{pr_number}", 2025-11-03T15:43:06.7948649Z  headers=headers, 2025-11-03T15:43:06.7949144Z  ) 2025-11-03T15:43:06.7949553Z  2025-11-03T15:43:06.7949962Z  if not json_response: 2025-11-03T15:43:06.7950637Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-11-03T15:43:06.7951459Z  return {} 2025-11-03T15:43:06.7951923Z  2025-11-03T15:43:06.7952327Z  return json_response 2025-11-03T15:43:06.7952818Z  2025-11-03T15:43:06.7953300Z  2025-11-03T15:43:06.7953949Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-11-03T15:43:06.7954746Z  """ 2025-11-03T15:43:06.7955352Z  Dynamically get the latest list of labels from the pull request 2025-11-03T15:43:06.7956074Z  """ 2025-11-03T15:43:06.7956628Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-11-03T15:43:06.7957303Z  return { 2025-11-03T15:43:06.7957979Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-11-03T15:43:06.7958731Z  } 2025-11-03T15:43:06.7959135Z  2025-11-03T15:43:06.7959513Z  2025-11-03T15:43:06.7959915Z def main() -> None: 2025-11-03T15:43:06.7960405Z  args = parse_args() 2025-11-03T15:43:06.7960899Z  2025-11-03T15:43:06.7961365Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-11-03T15:43:06.7961968Z  2025-11-03T15:43:06.7962395Z  # Check if the PR is opt-out 2025-11-03T15:43:06.7962949Z  if args.pr_number: 2025-11-03T15:43:06.7963826Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-11-03T15:43:06.7964645Z  if OPT_OUT_LABEL in labels: 2025-11-03T15:43:06.7965202Z  log.info( 2025-11-03T15:43:06.7965981Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-11-03T15:43:06.7966810Z  ) 2025-11-03T15:43:06.7967459Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.7968197Z  sys.exit() 2025-11-03T15:43:06.7968796Z  2025-11-03T15:43:06.7969177Z  try: 2025-11-03T15:43:06.7969682Z  rollout_state = get_rollout_state_from_issue( 2025-11-03T15:43:06.7970563Z  args.github_token, args.github_issue_repo, args.github_issue 2025-11-03T15:43:06.7971274Z  ) 2025-11-03T15:43:06.7971693Z  2025-11-03T15:43:06.7972140Z  username = get_potential_pr_author( 2025-11-03T15:43:06.7972746Z  args.github_token, 2025-11-03T15:43:06.7973403Z  args.github_repo, 2025-11-03T15:43:06.7973959Z  args.github_actor, 2025-11-03T15:43:06.7974519Z  args.github_ref_type, 2025-11-03T15:43:06.7975097Z  args.github_branch, 2025-11-03T15:43:06.7975629Z  ) 2025-11-03T15:43:06.7976045Z  2025-11-03T15:43:06.7976583Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-11-03T15:43:06.7977247Z  2025-11-03T15:43:06.7977711Z  runner_label_prefix = get_runner_prefix( 2025-11-03T15:43:06.7978318Z  rollout_state, 2025-11-03T15:43:06.7978895Z  (args.github_issue_owner, username), 2025-11-03T15:43:06.7979507Z  args.github_branch, 2025-11-03T15:43:06.7980089Z  args.eligible_experiments, 2025-11-03T15:43:06.7980697Z  args.opt_out_experiments, 2025-11-03T15:43:06.7981262Z  is_canary, 2025-11-03T15:43:06.7981756Z  ) 2025-11-03T15:43:06.7982163Z  2025-11-03T15:43:06.7982579Z  except Exception as e: 2025-11-03T15:43:06.7983211Z  log.error( 2025-11-03T15:43:06.7983997Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-11-03T15:43:06.7984943Z  ) 2025-11-03T15:43:06.7985365Z  2025-11-03T15:43:06.7985952Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.7986661Z  2025-11-03T15:43:06.7987040Z  2025-11-03T15:43:06.7987430Z if __name__ == "__main__": 2025-11-03T15:43:06.7987939Z  main() 2025-11-03T15:43:06.7988355Z  2025-11-03T15:43:06.7988733Z EOF 2025-11-03T15:43:06.7989124Z  2025-11-03T15:43:06.7989541Z cat runner_determinator.py 2025-11-03T15:43:06.8826383Z shell: /usr/bin/bash -e {0} 2025-11-03T15:43:06.8827267Z env: 2025-11-03T15:43:06.8828105Z GITHUB_TOKEN: *** 2025-11-03T15:43:06.8828586Z ISSUE_NUMBER: 5132 2025-11-03T15:43:06.8829089Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:43:06.8829676Z ISSUE_OWNER: 2025-11-03T15:43:06.8830126Z CHECK_EXPERIMENTS: 2025-11-03T15:43:06.8830626Z OPT_OUT_EXPERIMENTS: 2025-11-03T15:43:06.8831106Z PR_NUMBER: 2025-11-03T15:43:06.8831571Z ##[endgroup] 2025-11-03T15:43:06.9040755Z # flake8: noqa: G004 2025-11-03T15:43:06.9041127Z 2025-11-03T15:43:06.9041571Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-11-03T15:43:06.9042536Z # must be kept in sync. You can do it easily by running the following command: 2025-11-03T15:43:06.9043802Z # python .github/scripts/update_runner_determinator.py 2025-11-03T15:43:06.9044272Z 2025-11-03T15:43:06.9044434Z """ 2025-11-03T15:43:06.9045028Z This runner determinator is used to determine which set of runners to run a 2025-11-03T15:43:06.9045929Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-11-03T15:43:06.9046857Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-11-03T15:43:06.9047687Z of which runners should be used to run which job. 2025-11-03T15:43:06.9048102Z 2025-11-03T15:43:06.9048494Z The configuration has two parts, the settings and a list of opted-in users, 2025-11-03T15:43:06.9049616Z separated by a line containing "---". If the line is not present, the 2025-11-03T15:43:06.9050527Z settings are considered to be empty with only the second part, the user 2025-11-03T15:43:06.9051233Z list, defined. 2025-11-03T15:43:06.9051459Z 2025-11-03T15:43:06.9051835Z The first part is a YAML block that defines the rollout settings. This can be 2025-11-03T15:43:06.9052791Z used to define any settings that are needed to determine which runners to use. 2025-11-03T15:43:06.9054003Z It's fields are defined by the RolloutSettings class below. 2025-11-03T15:43:06.9054464Z 2025-11-03T15:43:06.9054842Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-11-03T15:43:06.9055727Z The user list is also a comma separated list of additional features or 2025-11-03T15:43:06.9056491Z experiments which the user could be opted in to. 2025-11-03T15:43:06.9056908Z 2025-11-03T15:43:06.9057113Z The user list has the following rules: 2025-11-03T15:43:06.9057466Z 2025-11-03T15:43:06.9057805Z - Users are GitHub usernames, which must start with the @ prefix 2025-11-03T15:43:06.9058698Z - Each user is also a comma-separated list of features/experiments to enable 2025-11-03T15:43:06.9059473Z - A "#" prefix opts the user out of all experiments 2025-11-03T15:43:06.9059873Z 2025-11-03T15:43:06.9060046Z Example config: 2025-11-03T15:43:06.9060529Z # A list of experiments that can be opted into. 2025-11-03T15:43:06.9061202Z # This defines the behavior they'll induce when opted into. 2025-11-03T15:43:06.9061835Z # Expected syntax is: 2025-11-03T15:43:06.9062480Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-11-03T15:43:06.9063714Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-11-03T15:43:06.9064337Z 2025-11-03T15:43:06.9064522Z experiments: 2025-11-03T15:43:06.9064912Z lf: 2025-11-03T15:43:06.9065291Z rollout_percent: 25 2025-11-03T15:43:06.9065925Z all_branches: false 2025-11-03T15:43:06.9066387Z default: true 2025-11-03T15:43:06.9066794Z --- 2025-11-03T15:43:06.9067020Z 2025-11-03T15:43:06.9067214Z # Opt-ins: 2025-11-03T15:43:06.9067799Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-11-03T15:43:06.9068700Z # and specifying experiments to enable in a comma-separated list. 2025-11-03T15:43:06.9069484Z # To always opt out of an experiment, prefix it with a "-". 2025-11-03T15:43:06.9070145Z # Experiments should be from the above list. 2025-11-03T15:43:06.9070536Z 2025-11-03T15:43:06.9070726Z @User1,-lf,split_build 2025-11-03T15:43:06.9071172Z @User2,lf 2025-11-03T15:43:06.9071562Z @User3,split_build 2025-11-03T15:43:06.9071968Z """ 2025-11-03T15:43:06.9072171Z 2025-11-03T15:43:06.9072339Z import json 2025-11-03T15:43:06.9072707Z import logging 2025-11-03T15:43:06.9073283Z import os 2025-11-03T15:43:06.9073668Z import random 2025-11-03T15:43:06.9074045Z import re 2025-11-03T15:43:06.9074428Z import sys 2025-11-03T15:43:06.9074879Z from argparse import ArgumentParser 2025-11-03T15:43:06.9075421Z from collections.abc import Iterable 2025-11-03T15:43:06.9075954Z from functools import cache 2025-11-03T15:43:06.9076432Z from logging import LogRecord 2025-11-03T15:43:06.9076928Z from typing import Any, NamedTuple 2025-11-03T15:43:06.9077479Z from urllib.request import Request, urlopen 2025-11-03T15:43:06.9077854Z 2025-11-03T15:43:06.9078021Z import yaml 2025-11-03T15:43:06.9078415Z from github import Auth, Github 2025-11-03T15:43:06.9078909Z from github.Issue import Issue 2025-11-03T15:43:06.9079224Z 2025-11-03T15:43:06.9079232Z 2025-11-03T15:43:06.9079459Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-11-03T15:43:06.9080173Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-11-03T15:43:06.9081064Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-11-03T15:43:06.9081635Z 2025-11-03T15:43:06.9081880Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-11-03T15:43:06.9082591Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-11-03T15:43:06.9083330Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-11-03T15:43:06.9083906Z OPT_OUT_LABEL = "no-runner-experiments" 2025-11-03T15:43:06.9084275Z 2025-11-03T15:43:06.9084475Z SETTING_EXPERIMENTS = "experiments" 2025-11-03T15:43:06.9084814Z 2025-11-03T15:43:06.9085014Z LF_FLEET_EXPERIMENT = "lf" 2025-11-03T15:43:06.9085483Z CANARY_FLEET_SUFFIX = ".c" 2025-11-03T15:43:06.9085770Z 2025-11-03T15:43:06.9085778Z 2025-11-03T15:43:06.9085975Z class Experiment(NamedTuple): 2025-11-03T15:43:06.9086468Z rollout_perc: float = ( 2025-11-03T15:43:06.9087123Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-11-03T15:43:06.9087821Z ) 2025-11-03T15:43:06.9088192Z all_branches: bool = ( 2025-11-03T15:43:06.9088823Z False # If True, the experiment is also enabled on the exception branches 2025-11-03T15:43:06.9089544Z ) 2025-11-03T15:43:06.9089961Z default: bool = ( 2025-11-03T15:43:06.9090545Z True # If True, the experiment is enabled by default for all queries 2025-11-03T15:43:06.9091199Z ) 2025-11-03T15:43:06.9091397Z 2025-11-03T15:43:06.9091604Z # Add more fields as needed 2025-11-03T15:43:06.9091914Z 2025-11-03T15:43:06.9091921Z 2025-11-03T15:43:06.9092109Z class Settings(NamedTuple): 2025-11-03T15:43:06.9092551Z """ 2025-11-03T15:43:06.9093164Z Settings for the experiments that can be opted into. 2025-11-03T15:43:06.9093772Z """ 2025-11-03T15:43:06.9093970Z 2025-11-03T15:43:06.9094185Z experiments: dict[str, Experiment] = {} 2025-11-03T15:43:06.9094556Z 2025-11-03T15:43:06.9094562Z 2025-11-03T15:43:06.9094774Z class ColorFormatter(logging.Formatter): 2025-11-03T15:43:06.9095403Z """Color codes the log messages based on the log level""" 2025-11-03T15:43:06.9095850Z 2025-11-03T15:43:06.9096019Z COLORS = { 2025-11-03T15:43:06.9096425Z "WARNING": "\033[33m", # Yellow 2025-11-03T15:43:06.9097086Z "ERROR": "\033[31m", # Red 2025-11-03T15:43:06.9097601Z "CRITICAL": "\033[31m", # Red 2025-11-03T15:43:06.9098108Z "INFO": "\033[0m", # Reset 2025-11-03T15:43:06.9098598Z "DEBUG": "\033[0m", # Reset 2025-11-03T15:43:06.9099072Z } 2025-11-03T15:43:06.9099271Z 2025-11-03T15:43:06.9099491Z def format(self, record: LogRecord) -> str: 2025-11-03T15:43:06.9100247Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-11-03T15:43:06.9101044Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-11-03T15:43:06.9101632Z return super().format(record) 2025-11-03T15:43:06.9101970Z 2025-11-03T15:43:06.9101977Z 2025-11-03T15:43:06.9102175Z handler = logging.StreamHandler() 2025-11-03T15:43:06.9102897Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-11-03T15:43:06.9103643Z 2025-11-03T15:43:06.9103899Z log = logging.getLogger(os.path.basename(__file__)) 2025-11-03T15:43:06.9104500Z log.addHandler(handler) 2025-11-03T15:43:06.9104949Z log.setLevel(logging.INFO) 2025-11-03T15:43:06.9105248Z 2025-11-03T15:43:06.9105254Z 2025-11-03T15:43:06.9105508Z def set_github_output(key: str, value: str) -> None: 2025-11-03T15:43:06.9106085Z """ 2025-11-03T15:43:06.9106589Z Defines outputs of the github action that invokes this script 2025-11-03T15:43:06.9107234Z """ 2025-11-03T15:43:06.9107600Z if not GITHUB_OUTPUT: 2025-11-03T15:43:06.9108694Z # 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.9109850Z log.warning( 2025-11-03T15:43:06.9110719Z "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.9111685Z ) 2025-11-03T15:43:06.9121776Z print(f"::set-output name={key}::{value}") 2025-11-03T15:43:06.9122401Z return 2025-11-03T15:43:06.9122655Z 2025-11-03T15:43:06.9123392Z with open(GITHUB_OUTPUT, "a") as f: 2025-11-03T15:43:06.9124091Z log.info(f"Setting output: {key}='{value}'") 2025-11-03T15:43:06.9124670Z f.write(f"{key}={value}\n") 2025-11-03T15:43:06.9125008Z 2025-11-03T15:43:06.9125016Z 2025-11-03T15:43:06.9125321Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-11-03T15:43:06.9125959Z return frozenset( 2025-11-03T15:43:06.9126569Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-11-03T15:43:06.9127278Z ) 2025-11-03T15:43:06.9127477Z 2025-11-03T15:43:06.9127485Z 2025-11-03T15:43:06.9127669Z def parse_args() -> Any: 2025-11-03T15:43:06.9128236Z parser = ArgumentParser("Get dynamic rollout settings") 2025-11-03T15:43:06.9129107Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-11-03T15:43:06.9129894Z parser.add_argument( 2025-11-03T15:43:06.9130359Z "--github-issue-repo", 2025-11-03T15:43:06.9130829Z type=str, 2025-11-03T15:43:06.9131239Z required=False, 2025-11-03T15:43:06.9131683Z default="pytorch/test-infra", 2025-11-03T15:43:06.9132227Z help="GitHub repo to get the issue", 2025-11-03T15:43:06.9132734Z ) 2025-11-03T15:43:06.9133323Z parser.add_argument( 2025-11-03T15:43:06.9133810Z "--github-repo", 2025-11-03T15:43:06.9134258Z type=str, 2025-11-03T15:43:06.9134682Z required=True, 2025-11-03T15:43:06.9135151Z help="GitHub repo where CI is running", 2025-11-03T15:43:06.9135690Z ) 2025-11-03T15:43:06.9187321Z parser.add_argument( 2025-11-03T15:43:06.9188709Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-11-03T15:43:06.9189831Z ) 2025-11-03T15:43:06.9190227Z parser.add_argument( 2025-11-03T15:43:06.9190900Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-11-03T15:43:06.9191600Z ) 2025-11-03T15:43:06.9191971Z parser.add_argument( 2025-11-03T15:43:06.9192855Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-11-03T15:43:06.9193843Z ) 2025-11-03T15:43:06.9194232Z parser.add_argument( 2025-11-03T15:43:06.9194910Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-11-03T15:43:06.9195655Z ) 2025-11-03T15:43:06.9196028Z parser.add_argument( 2025-11-03T15:43:06.9196496Z "--github-ref-type", 2025-11-03T15:43:06.9196958Z type=str, 2025-11-03T15:43:06.9197365Z required=True, 2025-11-03T15:43:06.9197853Z help="Current GitHub ref type, branch or tag", 2025-11-03T15:43:06.9198413Z ) 2025-11-03T15:43:06.9198796Z parser.add_argument( 2025-11-03T15:43:06.9199256Z "--eligible-experiments", 2025-11-03T15:43:06.9199785Z type=_str_comma_separated_to_set, 2025-11-03T15:43:06.9200303Z required=False, 2025-11-03T15:43:06.9200729Z default="", 2025-11-03T15:43:06.9201587Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-11-03T15:43:06.9202529Z ) 2025-11-03T15:43:06.9202903Z parser.add_argument( 2025-11-03T15:43:06.9203608Z "--opt-out-experiments", 2025-11-03T15:43:06.9204127Z type=_str_comma_separated_to_set, 2025-11-03T15:43:06.9204659Z required=False, 2025-11-03T15:43:06.9205084Z default="", 2025-11-03T15:43:06.9205482Z help=( 2025-11-03T15:43:06.9206162Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-11-03T15:43:06.9207306Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-11-03T15:43:06.9208146Z ), 2025-11-03T15:43:06.9208503Z ) 2025-11-03T15:43:06.9208878Z parser.add_argument( 2025-11-03T15:43:06.9209315Z "--pr-number", 2025-11-03T15:43:06.9209741Z type=str, 2025-11-03T15:43:06.9210142Z required=False, 2025-11-03T15:43:06.9210567Z default="", 2025-11-03T15:43:06.9211224Z help="the optional PR number where this is run", 2025-11-03T15:43:06.9211792Z ) 2025-11-03T15:43:06.9211987Z 2025-11-03T15:43:06.9212191Z return parser.parse_args() 2025-11-03T15:43:06.9212503Z 2025-11-03T15:43:06.9212510Z 2025-11-03T15:43:06.9212924Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.9213929Z auth = Auth.Token(github_token) 2025-11-03T15:43:06.9214443Z return Github(auth=auth) 2025-11-03T15:43:06.9214738Z 2025-11-03T15:43:06.9214745Z 2025-11-03T15:43:06.9215233Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-11-03T15:43:06.9216104Z repo = gh.get_repo(repo) 2025-11-03T15:43:06.9216608Z return repo.get_issue(number=issue_num) 2025-11-03T15:43:06.9216979Z 2025-11-03T15:43:06.9216986Z 2025-11-03T15:43:06.9217180Z def get_potential_pr_author( 2025-11-03T15:43:06.9217839Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-11-03T15:43:06.9218536Z ) -> str: 2025-11-03T15:43:06.9219066Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-11-03T15:43:06.9219878Z # Fetch the actual username from the original PR. The PR number is 2025-11-03T15:43:06.9220636Z # embedded in the tag name: ciflow// 2025-11-03T15:43:06.9221064Z 2025-11-03T15:43:06.9221260Z gh = get_gh_client(github_token) 2025-11-03T15:43:06.9221600Z 2025-11-03T15:43:06.9221873Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-11-03T15:43:06.9222502Z split_tag = ref_name.split("/") 2025-11-03T15:43:06.9223156Z if ( 2025-11-03T15:43:06.9223619Z len(split_tag) == 3 2025-11-03T15:43:06.9224114Z and split_tag[0] == "ciflow" 2025-11-03T15:43:06.9224652Z and split_tag[2].isnumeric() 2025-11-03T15:43:06.9225155Z ): 2025-11-03T15:43:06.9225548Z pr_number = split_tag[2] 2025-11-03T15:43:06.9226180Z try: 2025-11-03T15:43:06.9226636Z repository = gh.get_repo(repo) 2025-11-03T15:43:06.9227254Z pull = repository.get_pull(number=int(pr_number)) 2025-11-03T15:43:06.9227869Z except Exception as e: 2025-11-03T15:43:06.9228398Z raise Exception( # noqa: TRY002 2025-11-03T15:43:06.9229070Z f"issue with pull request {pr_number} from repo {repository}" 2025-11-03T15:43:06.9229727Z ) from e 2025-11-03T15:43:06.9230266Z return pull.user.login # type: ignore[no-any-return] 2025-11-03T15:43:06.9230974Z # In all other cases, return the original input username 2025-11-03T15:43:06.9231567Z return username 2025-11-03T15:43:06.9231813Z 2025-11-03T15:43:06.9231820Z 2025-11-03T15:43:06.9232052Z def is_exception_branch(branch: str) -> bool: 2025-11-03T15:43:06.9232612Z """ 2025-11-03T15:43:06.9233518Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-11-03T15:43:06.9234332Z """ 2025-11-03T15:43:06.9234946Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-11-03T15:43:06.9235484Z 2025-11-03T15:43:06.9235499Z 2025-11-03T15:43:06.9235709Z def load_yaml(yaml_text: str) -> Any: 2025-11-03T15:43:06.9236208Z try: 2025-11-03T15:43:06.9236603Z data = yaml.safe_load(yaml_text) 2025-11-03T15:43:06.9237112Z return data 2025-11-03T15:43:06.9237538Z except yaml.YAMLError: 2025-11-03T15:43:06.9238041Z log.exception("Error loading YAML") 2025-11-03T15:43:06.9238561Z raise 2025-11-03T15:43:06.9238777Z 2025-11-03T15:43:06.9238783Z 2025-11-03T15:43:06.9239218Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-11-03T15:43:06.9239970Z """ 2025-11-03T15:43:06.9240602Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-11-03T15:43:06.9241209Z 2025-11-03T15:43:06.9241704Z If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.9242493Z and the text below is the list of opted in users. 2025-11-03T15:43:06.9242902Z 2025-11-03T15:43:06.9243507Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-11-03T15:43:06.9244230Z """ 2025-11-03T15:43:06.9244682Z rollout_state_parts = rollout_state.split("---") 2025-11-03T15:43:06.9245322Z if len(rollout_state_parts) >= 2: 2025-11-03T15:43:06.9245942Z return rollout_state_parts[0], rollout_state_parts[1] 2025-11-03T15:43:06.9246542Z else: 2025-11-03T15:43:06.9246943Z return "", rollout_state 2025-11-03T15:43:06.9247258Z 2025-11-03T15:43:06.9247265Z 2025-11-03T15:43:06.9247484Z class UserOptins(dict[str, list[str]]): 2025-11-03T15:43:06.9247998Z """ 2025-11-03T15:43:06.9248552Z Dictionary of users with a list of features they have opted into 2025-11-03T15:43:06.9249224Z """ 2025-11-03T15:43:06.9249439Z 2025-11-03T15:43:06.9249446Z 2025-11-03T15:43:06.9249801Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-11-03T15:43:06.9250466Z """ 2025-11-03T15:43:06.9251187Z 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.9251882Z 2025-11-03T15:43:06.9252517Z 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.9253715Z - Example line: "@User1,lf,split_build" 2025-11-03T15:43:06.9254427Z - A "#" prefix indicates the user is opted out of all experiments 2025-11-03T15:43:06.9254912Z 2025-11-03T15:43:06.9254919Z 2025-11-03T15:43:06.9255085Z """ 2025-11-03T15:43:06.9255470Z optins = UserOptins() 2025-11-03T15:43:06.9255956Z for user in user_optin_text.split("\n"): 2025-11-03T15:43:06.9256518Z user = user.strip("\r\n\t -") 2025-11-03T15:43:06.9257080Z if not user or not user.startswith("@"): 2025-11-03T15:43:06.9257799Z # Not a valid user. Skip 2025-11-03T15:43:06.9258299Z continue 2025-11-03T15:43:06.9258545Z 2025-11-03T15:43:06.9258713Z if user: 2025-11-03T15:43:06.9259158Z usr_name = user.split(",")[0].strip("@") 2025-11-03T15:43:06.9259874Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-11-03T15:43:06.9260383Z 2025-11-03T15:43:06.9260556Z return optins 2025-11-03T15:43:06.9260798Z 2025-11-03T15:43:06.9260805Z 2025-11-03T15:43:06.9261102Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-11-03T15:43:06.9261710Z """ 2025-11-03T15:43:06.9262114Z Check if the experiment name is valid. 2025-11-03T15:43:06.9262644Z A valid name: 2025-11-03T15:43:06.9263470Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-11-03T15:43:06.9264471Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-11-03T15:43:06.9265232Z - Cannot contain spaces 2025-11-03T15:43:06.9265709Z """ 2025-11-03T15:43:06.9265917Z 2025-11-03T15:43:06.9266182Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-11-03T15:43:06.9266904Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-11-03T15:43:06.9267355Z 2025-11-03T15:43:06.9267528Z if valid: 2025-11-03T15:43:06.9267926Z return True 2025-11-03T15:43:06.9268171Z 2025-11-03T15:43:06.9268340Z log.error( 2025-11-03T15:43:06.9269839Z 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.9271482Z ) 2025-11-03T15:43:06.9271845Z return False 2025-11-03T15:43:06.9272091Z 2025-11-03T15:43:06.9272099Z 2025-11-03T15:43:06.9272413Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-11-03T15:43:06.9273226Z """ 2025-11-03T15:43:06.9274030Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-11-03T15:43:06.9274802Z """ 2025-11-03T15:43:06.9275163Z try: 2025-11-03T15:43:06.9275548Z if settings_text: 2025-11-03T15:43:06.9276282Z # 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.9277112Z # for easy reading 2025-11-03T15:43:06.9277917Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-11-03T15:43:06.9278831Z # the backtick character in shell commands. 2025-11-03T15:43:06.9279463Z backtick = chr(96) # backtick character 2025-11-03T15:43:06.9280136Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-11-03T15:43:06.9280844Z settings = load_yaml(settings_text) 2025-11-03T15:43:06.9281227Z 2025-11-03T15:43:06.9281666Z # For now we just load experiments. We can expand this if/when we add more settings 2025-11-03T15:43:06.9282460Z experiments = {} 2025-11-03T15:43:06.9282763Z 2025-11-03T15:43:06.9283403Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-11-03T15:43:06.9284221Z if not is_valid_experiment_name(exp_name): 2025-11-03T15:43:06.9285353Z # 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.9286421Z continue 2025-11-03T15:43:06.9286717Z 2025-11-03T15:43:06.9286927Z valid_settings = {} 2025-11-03T15:43:06.9287461Z for setting in exp_settings: 2025-11-03T15:43:06.9288043Z if setting not in Experiment._fields: 2025-11-03T15:43:06.9288600Z log.warning( 2025-11-03T15:43:06.9289341Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-11-03T15:43:06.9290228Z ) 2025-11-03T15:43:06.9290666Z else: 2025-11-03T15:43:06.9291189Z valid_settings[setting] = exp_settings[setting] 2025-11-03T15:43:06.9291624Z 2025-11-03T15:43:06.9291903Z experiments[exp_name] = Experiment(**valid_settings) 2025-11-03T15:43:06.9292555Z return Settings(experiments) 2025-11-03T15:43:06.9292916Z 2025-11-03T15:43:06.9293303Z except Exception: 2025-11-03T15:43:06.9293818Z log.exception("Failed to parse settings") 2025-11-03T15:43:06.9294214Z 2025-11-03T15:43:06.9294391Z return Settings() 2025-11-03T15:43:06.9294662Z 2025-11-03T15:43:06.9294670Z 2025-11-03T15:43:06.9294925Z def parse_settings(rollout_state: str) -> Settings: 2025-11-03T15:43:06.9295522Z """ 2025-11-03T15:43:06.9295968Z Parse settings, if any, from the rollout state. 2025-11-03T15:43:06.9296388Z 2025-11-03T15:43:06.9296749Z If the issue body contains "---" then the text above that is the settings 2025-11-03T15:43:06.9297537Z and the text below is the list of opted in users. 2025-11-03T15:43:06.9297944Z 2025-11-03T15:43:06.9298364Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-11-03T15:43:06.9299112Z """ 2025-11-03T15:43:06.9299668Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.9300445Z return parse_settings_from_text(settings_text) 2025-11-03T15:43:06.9300852Z 2025-11-03T15:43:06.9300859Z 2025-11-03T15:43:06.9301113Z def parse_users(rollout_state: str) -> UserOptins: 2025-11-03T15:43:06.9301687Z """ 2025-11-03T15:43:06.9302087Z Parse users from the rollout state. 2025-11-03T15:43:06.9302442Z 2025-11-03T15:43:06.9302608Z """ 2025-11-03T15:43:06.9303328Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-11-03T15:43:06.9304126Z return parse_user_opt_in_from_text(users_text) 2025-11-03T15:43:06.9304555Z 2025-11-03T15:43:06.9304561Z 2025-11-03T15:43:06.9305135Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.9305918Z """ 2025-11-03T15:43:06.9306352Z Check if a user is opted into an experiment 2025-11-03T15:43:06.9306908Z """ 2025-11-03T15:43:06.9307376Z return experiment_name in user_optins.get(user, []) 2025-11-03T15:43:06.9307809Z 2025-11-03T15:43:06.9307816Z 2025-11-03T15:43:06.9308251Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-11-03T15:43:06.9309009Z """ 2025-11-03T15:43:06.9309476Z Check if a user explicitly opted out of an experiment 2025-11-03T15:43:06.9310071Z """ 2025-11-03T15:43:06.9310594Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-11-03T15:43:06.9311343Z experiment_optout = "-" + experiment_name 2025-11-03T15:43:06.9312002Z if experiment_optout not in user_optins.get(user, []): 2025-11-03T15:43:06.9312631Z return False 2025-11-03T15:43:06.9312895Z 2025-11-03T15:43:06.9313406Z if is_user_opted_in(user, user_optins, experiment_name): 2025-11-03T15:43:06.9314086Z log.warning( 2025-11-03T15:43:06.9314913Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-11-03T15:43:06.9315815Z ) 2025-11-03T15:43:06.9316033Z 2025-11-03T15:43:06.9316209Z return True 2025-11-03T15:43:06.9316446Z 2025-11-03T15:43:06.9316453Z 2025-11-03T15:43:06.9316636Z def get_runner_prefix( 2025-11-03T15:43:06.9317090Z rollout_state: str, 2025-11-03T15:43:06.9317555Z workflow_requestors: Iterable[str], 2025-11-03T15:43:06.9318092Z branch: str, 2025-11-03T15:43:06.9318591Z eligible_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.9319274Z opt_out_experiments: frozenset[str] = frozenset(), 2025-11-03T15:43:06.9319873Z is_canary: bool = False, 2025-11-03T15:43:06.9320342Z ) -> str: 2025-11-03T15:43:06.9320915Z settings = parse_settings(rollout_state) 2025-11-03T15:43:06.9321515Z user_optins = parse_users(rollout_state) 2025-11-03T15:43:06.9321891Z 2025-11-03T15:43:06.9322072Z fleet_prefix = "" 2025-11-03T15:43:06.9322495Z prefixes = [] 2025-11-03T15:43:06.9323343Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-11-03T15:43:06.9324323Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-11-03T15:43:06.9325048Z log.info( 2025-11-03T15:43:06.9325737Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-11-03T15:43:06.9326506Z ) 2025-11-03T15:43:06.9326899Z continue 2025-11-03T15:43:06.9327152Z 2025-11-03T15:43:06.9327342Z if opt_out_experiments: 2025-11-03T15:43:06.9327894Z if experiment_name in opt_out_experiments: 2025-11-03T15:43:06.9328545Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-11-03T15:43:06.9329151Z log.info( 2025-11-03T15:43:06.9330085Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-11-03T15:43:06.9331092Z ) 2025-11-03T15:43:06.9331494Z continue 2025-11-03T15:43:06.9331763Z 2025-11-03T15:43:06.9331953Z if eligible_experiments: 2025-11-03T15:43:06.9332555Z if experiment_name not in eligible_experiments: 2025-11-03T15:43:06.9333371Z exp_list = ", ".join(eligible_experiments) 2025-11-03T15:43:06.9333958Z log.info( 2025-11-03T15:43:06.9334780Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-11-03T15:43:06.9335634Z ) 2025-11-03T15:43:06.9336041Z continue 2025-11-03T15:43:06.9336523Z elif not experiment_settings.default: 2025-11-03T15:43:06.9337067Z log.info( 2025-11-03T15:43:06.9337867Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-11-03T15:43:06.9338627Z ) 2025-11-03T15:43:06.9339011Z continue 2025-11-03T15:43:06.9339270Z 2025-11-03T15:43:06.9339553Z # Is any workflow_requestor opted out to this experiment? 2025-11-03T15:43:06.9340177Z opted_out_users = [ 2025-11-03T15:43:06.9340636Z requestor 2025-11-03T15:43:06.9341098Z for requestor in workflow_requestors 2025-11-03T15:43:06.9341779Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.9342425Z ] 2025-11-03T15:43:06.9342633Z 2025-11-03T15:43:06.9342814Z if opted_out_users: 2025-11-03T15:43:06.9343464Z log.info( 2025-11-03T15:43:06.9344095Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-11-03T15:43:06.9344811Z ) 2025-11-03T15:43:06.9345191Z continue 2025-11-03T15:43:06.9345462Z 2025-11-03T15:43:06.9345754Z # Is any workflow_requestor opted in to this experiment? 2025-11-03T15:43:06.9346399Z opted_in_users = [ 2025-11-03T15:43:06.9346848Z requestor 2025-11-03T15:43:06.9347320Z for requestor in workflow_requestors 2025-11-03T15:43:06.9348011Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-11-03T15:43:06.9348655Z ] 2025-11-03T15:43:06.9348860Z 2025-11-03T15:43:06.9349036Z enabled = False 2025-11-03T15:43:06.9349474Z if opted_in_users: 2025-11-03T15:43:06.9349924Z log.info( 2025-11-03T15:43:06.9350543Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-11-03T15:43:06.9351249Z ) 2025-11-03T15:43:06.9351647Z enabled = True 2025-11-03T15:43:06.9351930Z 2025-11-03T15:43:06.9352160Z elif experiment_settings.rollout_perc: 2025-11-03T15:43:06.9353164Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-11-03T15:43:06.9354365Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-11-03T15:43:06.9355135Z log.info( 2025-11-03T15:43:06.9356023Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-11-03T15:43:06.9356962Z ) 2025-11-03T15:43:06.9357373Z enabled = True 2025-11-03T15:43:06.9357674Z 2025-11-03T15:43:06.9357844Z if enabled: 2025-11-03T15:43:06.9358274Z label = experiment_name 2025-11-03T15:43:06.9358837Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-11-03T15:43:06.9359678Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-11-03T15:43:06.9360570Z # - If it's enabled, then we always list it's prefix first 2025-11-03T15:43:06.9361346Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-11-03T15:43:06.9362022Z if is_canary: 2025-11-03T15:43:06.9362520Z label += CANARY_FLEET_SUFFIX 2025-11-03T15:43:06.9363694Z fleet_prefix = label 2025-11-03T15:43:06.9364245Z else: 2025-11-03T15:43:06.9364671Z prefixes.append(label) 2025-11-03T15:43:06.9365024Z 2025-11-03T15:43:06.9365215Z if len(prefixes) > 1: 2025-11-03T15:43:06.9365658Z log.error( 2025-11-03T15:43:06.9366709Z 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.9367826Z ) 2025-11-03T15:43:06.9368219Z prefixes = prefixes[:1] 2025-11-03T15:43:06.9368528Z 2025-11-03T15:43:06.9368727Z # Fleet always comes first 2025-11-03T15:43:06.9369195Z if fleet_prefix: 2025-11-03T15:43:06.9369652Z prefixes.insert(0, fleet_prefix) 2025-11-03T15:43:06.9370023Z 2025-11-03T15:43:06.9370428Z return ".".join(prefixes) + "." if prefixes else "" 2025-11-03T15:43:06.9370866Z 2025-11-03T15:43:06.9370875Z 2025-11-03T15:43:06.9371328Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-11-03T15:43:06.9372110Z """ 2025-11-03T15:43:06.9372696Z Gets the first comment of the issue, which contains the desired rollout state. 2025-11-03T15:43:06.9373433Z 2025-11-03T15:43:06.9373827Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-11-03T15:43:06.9374536Z """ 2025-11-03T15:43:06.9374933Z gh = get_gh_client(github_token) 2025-11-03T15:43:06.9375473Z issue = get_issue(gh, repo, issue_num) 2025-11-03T15:43:06.9376108Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-11-03T15:43:06.9376550Z 2025-11-03T15:43:06.9376558Z 2025-11-03T15:43:06.9376971Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-11-03T15:43:06.9377737Z for _ in range(num_retries): 2025-11-03T15:43:06.9378225Z try: 2025-11-03T15:43:06.9378656Z req = Request(url=url, headers=headers) 2025-11-03T15:43:06.9379338Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-11-03T15:43:06.9379987Z return json.loads(content) 2025-11-03T15:43:06.9380528Z except Exception as e: 2025-11-03T15:43:06.9381084Z log.warning(f"Could not download {url}: {e}") 2025-11-03T15:43:06.9381492Z 2025-11-03T15:43:06.9381878Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-11-03T15:43:06.9382648Z return {} 2025-11-03T15:43:06.9382873Z 2025-11-03T15:43:06.9382880Z 2025-11-03T15:43:06.9383151Z @cache 2025-11-03T15:43:06.9383799Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-11-03T15:43:06.9384559Z """ 2025-11-03T15:43:06.9384961Z Dynamically get PR information 2025-11-03T15:43:06.9385590Z """ 2025-11-03T15:43:06.9386081Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-11-03T15:43:06.9386715Z headers = { 2025-11-03T15:43:06.9387174Z "Accept": "application/vnd.github.v3+json", 2025-11-03T15:43:06.9387784Z "Authorization": f"token {github_token}", 2025-11-03T15:43:06.9388332Z } 2025-11-03T15:43:06.9388761Z json_response: dict[str, Any] = download_json( 2025-11-03T15:43:06.9389364Z url=f"{github_api}/issues/{pr_number}", 2025-11-03T15:43:06.9389919Z headers=headers, 2025-11-03T15:43:06.9390343Z ) 2025-11-03T15:43:06.9390547Z 2025-11-03T15:43:06.9390737Z if not json_response: 2025-11-03T15:43:06.9391315Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-11-03T15:43:06.9391930Z return {} 2025-11-03T15:43:06.9392166Z 2025-11-03T15:43:06.9392352Z return json_response 2025-11-03T15:43:06.9392628Z 2025-11-03T15:43:06.9392635Z 2025-11-03T15:43:06.9393147Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-11-03T15:43:06.9393910Z """ 2025-11-03T15:43:06.9394444Z Dynamically get the latest list of labels from the pull request 2025-11-03T15:43:06.9395105Z """ 2025-11-03T15:43:06.9395586Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-11-03T15:43:06.9396188Z return { 2025-11-03T15:43:06.9396798Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-11-03T15:43:06.9397506Z } 2025-11-03T15:43:06.9397712Z 2025-11-03T15:43:06.9397718Z 2025-11-03T15:43:06.9397892Z def main() -> None: 2025-11-03T15:43:06.9398317Z args = parse_args() 2025-11-03T15:43:06.9398598Z 2025-11-03T15:43:06.9398821Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-11-03T15:43:06.9399212Z 2025-11-03T15:43:06.9399411Z # Check if the PR is opt-out 2025-11-03T15:43:06.9399903Z if args.pr_number: 2025-11-03T15:43:06.9400572Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-11-03T15:43:06.9401453Z if OPT_OUT_LABEL in labels: 2025-11-03T15:43:06.9401997Z log.info( 2025-11-03T15:43:06.9402691Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-11-03T15:43:06.9403592Z ) 2025-11-03T15:43:06.9404153Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.9404831Z sys.exit() 2025-11-03T15:43:06.9405088Z 2025-11-03T15:43:06.9405259Z try: 2025-11-03T15:43:06.9405690Z rollout_state = get_rollout_state_from_issue( 2025-11-03T15:43:06.9406404Z args.github_token, args.github_issue_repo, args.github_issue 2025-11-03T15:43:06.9407041Z ) 2025-11-03T15:43:06.9407249Z 2025-11-03T15:43:06.9407462Z username = get_potential_pr_author( 2025-11-03T15:43:06.9408007Z args.github_token, 2025-11-03T15:43:06.9408495Z args.github_repo, 2025-11-03T15:43:06.9408978Z args.github_actor, 2025-11-03T15:43:06.9409468Z args.github_ref_type, 2025-11-03T15:43:06.9409977Z args.github_branch, 2025-11-03T15:43:06.9410443Z ) 2025-11-03T15:43:06.9410648Z 2025-11-03T15:43:06.9410945Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-11-03T15:43:06.9411401Z 2025-11-03T15:43:06.9411623Z runner_label_prefix = get_runner_prefix( 2025-11-03T15:43:06.9412194Z rollout_state, 2025-11-03T15:43:06.9412682Z (args.github_issue_owner, username), 2025-11-03T15:43:06.9413351Z args.github_branch, 2025-11-03T15:43:06.9413862Z args.eligible_experiments, 2025-11-03T15:43:06.9414407Z args.opt_out_experiments, 2025-11-03T15:43:06.9414922Z is_canary, 2025-11-03T15:43:06.9415338Z ) 2025-11-03T15:43:06.9415544Z 2025-11-03T15:43:06.9415741Z except Exception as e: 2025-11-03T15:43:06.9416195Z log.error( 2025-11-03T15:43:06.9416873Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-11-03T15:43:06.9417774Z ) 2025-11-03T15:43:06.9417985Z 2025-11-03T15:43:06.9418317Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-11-03T15:43:06.9418817Z 2025-11-03T15:43:06.9418823Z 2025-11-03T15:43:06.9419010Z if __name__ == "__main__": 2025-11-03T15:43:06.9419449Z main() 2025-11-03T15:43:06.9419657Z 2025-11-03T15:43:06.9512773Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-11-03T15:43:06.9513897Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-11-03T15:43:06.9547445Z shell: /usr/bin/bash -e {0} 2025-11-03T15:43:06.9547929Z env: 2025-11-03T15:43:06.9548566Z GITHUB_TOKEN: *** 2025-11-03T15:43:06.9548986Z ISSUE_NUMBER: 5132 2025-11-03T15:43:06.9549440Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:43:06.9549956Z ISSUE_OWNER: 2025-11-03T15:43:06.9550359Z CHECK_EXPERIMENTS: 2025-11-03T15:43:06.9550791Z OPT_OUT_EXPERIMENTS: 2025-11-03T15:43:06.9551230Z PR_NUMBER: 2025-11-03T15:43:06.9551613Z ##[endgroup] 2025-11-03T15:43:07.5412685Z Defaulting to user installation because normal site-packages is not writeable 2025-11-03T15:43:08.2374692Z Collecting urllib3==1.26.18 2025-11-03T15:43:08.2737933Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-11-03T15:43:08.2946741Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.0 MB/s eta 0:00:00 2025-11-03T15:43:08.3169518Z Collecting PyGithub==2.3.0 2025-11-03T15:43:08.3253745Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-11-03T15:43:08.3712650Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-11-03T15:43:08.3749737Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-11-03T15:43:08.3794792Z 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.3810863Z 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.3825810Z 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:08.4087067Z Collecting Deprecated (from PyGithub==2.3.0) 2025-11-03T15:43:08.4126330Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-11-03T15:43:08.4350604Z 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:08.5726445Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-11-03T15:43:08.5814536Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-11-03T15:43:08.7369319Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-11-03T15:43:08.7411403Z 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:08.7600955Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-11-03T15:43:08.7638103Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-11-03T15:43:08.7879218Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-11-03T15:43:08.7932165Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 42.1 MB/s eta 0:00:00 2025-11-03T15:43:08.7987990Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-11-03T15:43:08.8048872Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 80.6 MB/s eta 0:00:00 2025-11-03T15:43:08.8084487Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-11-03T15:43:08.8201011Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 141.2 MB/s eta 0:00:00 2025-11-03T15:43:08.8238037Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-11-03T15:43:08.8294157Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-11-03T15:43:08.8342246Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 68.0 MB/s eta 0:00:00 2025-11-03T15:43:08.8379024Z 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:08.8421703Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.4/121.4 kB 44.4 MB/s eta 0:00:00 2025-11-03T15:43:08.8540848Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-11-03T15:43:08.8581279Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 44.8 MB/s eta 0:00:00 2025-11-03T15:43:09.1557921Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-11-03T15:43:09.7010900Z 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:09.7878841Z ##[group]Run curr_branch="main" 2025-11-03T15:43:09.7879143Z curr_branch="main" 2025-11-03T15:43:09.7879369Z curr_ref_type="branch" 2025-11-03T15:43:09.7879616Z echo "Current branch is '$curr_branch'" 2025-11-03T15:43:09.7879909Z  2025-11-03T15:43:09.7880089Z python3 runner_determinator.py \ 2025-11-03T15:43:09.7880365Z  --github-token "$GITHUB_TOKEN" \ 2025-11-03T15:43:09.7880643Z  --github-issue "$ISSUE_NUMBER" \ 2025-11-03T15:43:09.7880895Z  --github-branch "$curr_branch" \ 2025-11-03T15:43:09.7881156Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-11-03T15:43:09.7881430Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-11-03T15:43:09.7881708Z  --github-ref-type "$curr_ref_type" \ 2025-11-03T15:43:09.7881972Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-11-03T15:43:09.7882270Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-11-03T15:43:09.7882630Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-11-03T15:43:09.7882916Z  --pr-number "${PR_NUMBER}" 2025-11-03T15:43:09.7917475Z shell: /usr/bin/bash -e {0} 2025-11-03T15:43:09.7917696Z env: 2025-11-03T15:43:09.7918275Z GITHUB_TOKEN: *** 2025-11-03T15:43:09.7918467Z ISSUE_NUMBER: 5132 2025-11-03T15:43:09.7918682Z TRIGGERING_ACTOR: pytorchmergebot 2025-11-03T15:43:09.7918910Z ISSUE_OWNER: 2025-11-03T15:43:09.7919083Z CHECK_EXPERIMENTS: 2025-11-03T15:43:09.7919264Z OPT_OUT_EXPERIMENTS: 2025-11-03T15:43:09.7919443Z PR_NUMBER: 2025-11-03T15:43:09.7919602Z ##[endgroup] 2025-11-03T15:43:09.7971124Z Current branch is 'main' 2025-11-03T15:43:11.4227931Z INFO : Based on rollout percentage of 60%, enabling experiment lf. 2025-11-03T15:43:11.4229039Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-11-03T15:43:11.4229674Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-11-03T15:43:11.4230247Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-11-03T15:43:11.4230709Z INFO : Setting output: label-type='lf.' 2025-11-03T15:43:11.4556073Z Evaluate and set job outputs 2025-11-03T15:43:11.4562200Z Set output 'label-type' 2025-11-03T15:43:11.4564486Z Cleaning up orphan processes