2025-08-26T19:40:13.0912263Z Current runner version: '2.328.0' 2025-08-26T19:40:13.0936701Z ##[group]Runner Image Provisioner 2025-08-26T19:40:13.0937605Z Hosted Compute Agent 2025-08-26T19:40:13.0938135Z Version: 20250818.377 2025-08-26T19:40:13.0938743Z Commit: 3c593e9f75fe0b87e893bca80d6e12ba089c61fc 2025-08-26T19:40:13.0939510Z Build Date: 2025-08-18T14:52:18Z 2025-08-26T19:40:13.0940414Z ##[endgroup] 2025-08-26T19:40:13.0940944Z ##[group]Operating System 2025-08-26T19:40:13.0941617Z Ubuntu 2025-08-26T19:40:13.0942084Z 24.04.2 2025-08-26T19:40:13.0942549Z LTS 2025-08-26T19:40:13.0943087Z ##[endgroup] 2025-08-26T19:40:13.0943567Z ##[group]Runner Image 2025-08-26T19:40:13.0944148Z Image: ubuntu-24.04 2025-08-26T19:40:13.0944712Z Version: 20250818.1.0 2025-08-26T19:40:13.0945831Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250818.1/images/ubuntu/Ubuntu2404-Readme.md 2025-08-26T19:40:13.0947579Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250818.1 2025-08-26T19:40:13.0948627Z ##[endgroup] 2025-08-26T19:40:13.0949987Z ##[group]GITHUB_TOKEN Permissions 2025-08-26T19:40:13.0952282Z Contents: read 2025-08-26T19:40:13.0952804Z Metadata: read 2025-08-26T19:40:13.0953751Z ##[endgroup] 2025-08-26T19:40:13.0956116Z Secret source: Actions 2025-08-26T19:40:13.0957001Z Prepare workflow directory 2025-08-26T19:40:13.1469144Z Prepare all required actions 2025-08-26T19:40:13.1526764Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (262640fd220236042fbf4443cc163c8838c84c3d) 2025-08-26T19:40:13.1532203Z ##[group] Inputs 2025-08-26T19:40:13.1532849Z check_experiments: 2025-08-26T19:40:13.1533436Z opt_out_experiments: 2025-08-26T19:40:13.1533978Z triggering_actor: pytorchmergebot 2025-08-26T19:40:13.1534643Z issue_owner: 2025-08-26T19:40:13.1535194Z curr_branch: main 2025-08-26T19:40:13.1535673Z curr_ref_type: branch 2025-08-26T19:40:13.1536356Z issue_number: 5132 2025-08-26T19:40:13.1536873Z ##[endgroup] 2025-08-26T19:40:13.1537574Z Complete job name: before-test / get-label-type / runner-determinator 2025-08-26T19:40:13.7015565Z ##[group]Run cat < runner_determinator.py 2025-08-26T19:40:13.7018528Z cat < runner_determinator.py 2025-08-26T19:40:13.7019519Z # flake8: noqa: G004 2025-08-26T19:40:13.7020424Z  2025-08-26T19:40:13.7021669Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:40:13.7023318Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:40:13.7024811Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:40:13.7025977Z  2025-08-26T19:40:13.7026585Z """ 2025-08-26T19:40:13.7027648Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:40:13.7029269Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:40:13.7031360Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:40:13.7032914Z of which runners should be used to run which job. 2025-08-26T19:40:13.7033983Z  2025-08-26T19:40:13.7035035Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:40:13.7036727Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:40:13.7038366Z settings are considered to be empty with only the second part, the user 2025-08-26T19:40:13.7039643Z list, defined. 2025-08-26T19:40:13.7040558Z  2025-08-26T19:40:13.7041626Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:40:13.7043323Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:40:13.7044918Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:40:13.7046032Z  2025-08-26T19:40:13.7047368Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:40:13.7048937Z The user list is also a comma separated list of additional features or 2025-08-26T19:40:13.7050560Z experiments which the user could be opted in to. 2025-08-26T19:40:13.7051603Z  2025-08-26T19:40:13.7052303Z The user list has the following rules: 2025-08-26T19:40:13.7053306Z  2025-08-26T19:40:13.7054303Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:40:13.7055884Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:40:13.7057315Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:40:13.7058314Z  2025-08-26T19:40:13.7058960Z Example config: 2025-08-26T19:40:13.7060132Z  # A list of experiments that can be opted into. 2025-08-26T19:40:13.7061431Z  # This defines the behavior they'll induce when opted into. 2025-08-26T19:40:13.7062567Z  # Expected syntax is: 2025-08-26T19:40:13.7063864Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:40:13.7065473Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:40:13.7066681Z  2025-08-26T19:40:13.7161311Z  experiments: 2025-08-26T19:40:13.7161980Z  lf: 2025-08-26T19:40:13.7162599Z  rollout_percent: 25 2025-08-26T19:40:13.7163318Z  all_branches: false 2025-08-26T19:40:13.7164011Z  default: true 2025-08-26T19:40:13.7164590Z  --- 2025-08-26T19:40:13.7165064Z  2025-08-26T19:40:13.7165531Z  # Opt-ins: 2025-08-26T19:40:13.7166361Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:40:13.7167894Z  # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:40:13.7168986Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:40:13.7170038Z  # Experiments should be from the above list. 2025-08-26T19:40:13.7170782Z  2025-08-26T19:40:13.7171270Z  @User1,-lf,split_build 2025-08-26T19:40:13.7171905Z  @User2,lf 2025-08-26T19:40:13.7172472Z  @User3,split_build 2025-08-26T19:40:13.7173071Z """ 2025-08-26T19:40:13.7173532Z  2025-08-26T19:40:13.7173984Z import json 2025-08-26T19:40:13.7174511Z import logging 2025-08-26T19:40:13.7175052Z import os 2025-08-26T19:40:13.7175563Z import random 2025-08-26T19:40:13.7176094Z import re 2025-08-26T19:40:13.7176603Z import sys 2025-08-26T19:40:13.7177178Z from argparse import ArgumentParser 2025-08-26T19:40:13.7178005Z from collections.abc import Iterable 2025-08-26T19:40:13.7178743Z from functools import cache 2025-08-26T19:40:13.7179407Z from logging import LogRecord 2025-08-26T19:40:13.7180210Z from typing import Any, NamedTuple 2025-08-26T19:40:13.7180982Z from urllib.request import Request, urlopen 2025-08-26T19:40:13.7181722Z  2025-08-26T19:40:13.7182211Z import yaml 2025-08-26T19:40:13.7182778Z from github import Auth, Github 2025-08-26T19:40:13.7183474Z from github.Issue import Issue 2025-08-26T19:40:13.7184113Z  2025-08-26T19:40:13.7184524Z  2025-08-26T19:40:13.7185045Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:40:13.7185968Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:40:13.7187105Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:40:13.7188003Z  2025-08-26T19:40:13.7188715Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:40:13.7189454Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:40:13.7190261Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:40:13.7191033Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:40:13.7191683Z  2025-08-26T19:40:13.7192159Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:40:13.7192800Z  2025-08-26T19:40:13.7193244Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:40:13.7193855Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:40:13.7194427Z  2025-08-26T19:40:13.7194832Z  2025-08-26T19:40:13.7195296Z class Experiment(NamedTuple): 2025-08-26T19:40:13.7195922Z  rollout_perc: float = ( 2025-08-26T19:40:13.7196781Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:40:13.7197636Z  ) 2025-08-26T19:40:13.7198105Z  all_branches: bool = ( 2025-08-26T19:40:13.7198956Z  False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:40:13.7200074Z  ) 2025-08-26T19:40:13.7200545Z  default: bool = ( 2025-08-26T19:40:13.7201303Z  True # If True, the experiment is enabled by default for all queries 2025-08-26T19:40:13.7202114Z  ) 2025-08-26T19:40:13.7202547Z  2025-08-26T19:40:13.7202997Z  # Add more fields as needed 2025-08-26T19:40:13.7203588Z  2025-08-26T19:40:13.7203999Z  2025-08-26T19:40:13.7204451Z class Settings(NamedTuple): 2025-08-26T19:40:13.7205027Z  """ 2025-08-26T19:40:13.7205623Z  Settings for the experiments that can be opted into. 2025-08-26T19:40:13.7206351Z  """ 2025-08-26T19:40:13.7206792Z  2025-08-26T19:40:13.7207280Z  experiments: dict[str, Experiment] = {} 2025-08-26T19:40:13.7207939Z  2025-08-26T19:40:13.7208479Z  2025-08-26T19:40:13.7208987Z class ColorFormatter(logging.Formatter): 2025-08-26T19:40:13.7209920Z  """Color codes the log messages based on the log level""" 2025-08-26T19:40:13.7210665Z  2025-08-26T19:40:13.7211090Z  COLORS = { 2025-08-26T19:40:13.7211638Z  "WARNING": "\033[33m", # Yellow 2025-08-26T19:40:13.7212299Z  "ERROR": "\033[31m", # Red 2025-08-26T19:40:13.7212930Z  "CRITICAL": "\033[31m", # Red 2025-08-26T19:40:13.7213574Z  "INFO": "\033[0m", # Reset 2025-08-26T19:40:13.7214208Z  "DEBUG": "\033[0m", # Reset 2025-08-26T19:40:13.7214816Z  } 2025-08-26T19:40:13.7215256Z  2025-08-26T19:40:13.7215764Z  def format(self, record: LogRecord) -> str: 2025-08-26T19:40:13.7216750Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:40:13.7217748Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:40:13.7218478Z  return super().format(record) 2025-08-26T19:40:13.7219091Z  2025-08-26T19:40:13.7219504Z  2025-08-26T19:40:13.7220073Z handler = logging.StreamHandler() 2025-08-26T19:40:13.7221019Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:40:13.7221960Z  2025-08-26T19:40:13.7222514Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:40:13.7223268Z log.addHandler(handler) 2025-08-26T19:40:13.7223859Z log.setLevel(logging.INFO) 2025-08-26T19:40:13.7224428Z  2025-08-26T19:40:13.7224828Z  2025-08-26T19:40:13.7225387Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:40:13.7226125Z  """ 2025-08-26T19:40:13.7226782Z  Defines outputs of the github action that invokes this script 2025-08-26T19:40:13.7227740Z  """ 2025-08-26T19:40:13.7228253Z  if not GITHUB_OUTPUT: 2025-08-26T19:40:13.7229661Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-26T19:40:13.7231259Z  log.warning( 2025-08-26T19:40:13.7232399Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-26T19:40:13.7233578Z  ) 2025-08-26T19:40:13.7234131Z  print(f"::set-output name={key}::{value}") 2025-08-26T19:40:13.7234823Z  return 2025-08-26T19:40:13.7235311Z  2025-08-26T19:40:13.7235790Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:40:13.7236517Z  log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:40:13.7237242Z  f.write(f"{key}={value}\n") 2025-08-26T19:40:13.7237867Z  2025-08-26T19:40:13.7238281Z  2025-08-26T19:40:13.7238919Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:40:13.7240293Z  return frozenset( 2025-08-26T19:40:13.7241143Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:40:13.7242010Z  ) 2025-08-26T19:40:13.7242480Z  2025-08-26T19:40:13.7242895Z  2025-08-26T19:40:13.7243339Z def parse_args() -> Any: 2025-08-26T19:40:13.7244096Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:40:13.7245203Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:40:13.7246180Z  parser.add_argument( 2025-08-26T19:40:13.7246780Z  "--github-issue-repo", 2025-08-26T19:40:13.7247393Z  type=str, 2025-08-26T19:40:13.7247944Z  required=False, 2025-08-26T19:40:13.7248695Z  default="pytorch/test-infra", 2025-08-26T19:40:13.7249401Z  help="GitHub repo to get the issue", 2025-08-26T19:40:13.7250159Z  ) 2025-08-26T19:40:13.7250638Z  parser.add_argument( 2025-08-26T19:40:13.7251226Z  "--github-repo", 2025-08-26T19:40:13.7251798Z  type=str, 2025-08-26T19:40:13.7252336Z  required=True, 2025-08-26T19:40:13.7252977Z  help="GitHub repo where CI is running", 2025-08-26T19:40:13.7253640Z  ) 2025-08-26T19:40:13.7254111Z  parser.add_argument( 2025-08-26T19:40:13.7254925Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:40:13.7255766Z  ) 2025-08-26T19:40:13.7256236Z  parser.add_argument( 2025-08-26T19:40:13.7257068Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:40:13.7257957Z  ) 2025-08-26T19:40:13.7258425Z  parser.add_argument( 2025-08-26T19:40:13.7259298Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:40:13.7260271Z  ) 2025-08-26T19:40:13.7260741Z  parser.add_argument( 2025-08-26T19:40:13.7261621Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:40:13.7262525Z  ) 2025-08-26T19:40:13.7263019Z  parser.add_argument( 2025-08-26T19:40:13.7263613Z  "--github-ref-type", 2025-08-26T19:40:13.7264215Z  type=str, 2025-08-26T19:40:13.7264758Z  required=True, 2025-08-26T19:40:13.7265424Z  help="Current GitHub ref type, branch or tag", 2025-08-26T19:40:13.7266112Z  ) 2025-08-26T19:40:13.7266584Z  parser.add_argument( 2025-08-26T19:40:13.7267330Z  "--eligible-experiments", 2025-08-26T19:40:13.7268019Z  type=_str_comma_separated_to_set, 2025-08-26T19:40:13.7268679Z  required=False, 2025-08-26T19:40:13.7269251Z  default="", 2025-08-26T19:40:13.7270474Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:40:13.7271659Z  ) 2025-08-26T19:40:13.7272129Z  parser.add_argument( 2025-08-26T19:40:13.7272729Z  "--opt-out-experiments", 2025-08-26T19:40:13.7273390Z  type=_str_comma_separated_to_set, 2025-08-26T19:40:13.7274044Z  required=False, 2025-08-26T19:40:13.7274617Z  default="", 2025-08-26T19:40:13.7275145Z  help=( 2025-08-26T19:40:13.7276012Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:40:13.7277475Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:40:13.7278534Z  ), 2025-08-26T19:40:13.7279000Z  ) 2025-08-26T19:40:13.7279465Z  parser.add_argument( 2025-08-26T19:40:13.7280138Z  "--pr-number", 2025-08-26T19:40:13.7280692Z  type=str, 2025-08-26T19:40:13.7281220Z  required=False, 2025-08-26T19:40:13.7281786Z  default="", 2025-08-26T19:40:13.7282450Z  help="the optional PR number where this is run", 2025-08-26T19:40:13.7283159Z  ) 2025-08-26T19:40:13.7283601Z  2025-08-26T19:40:13.7284063Z  return parser.parse_args() 2025-08-26T19:40:13.7284657Z  2025-08-26T19:40:13.7285069Z  2025-08-26T19:40:13.7285818Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:40:13.7286921Z  auth = Auth.Token(github_token) 2025-08-26T19:40:13.7287599Z  return Github(auth=auth) 2025-08-26T19:40:13.7288184Z  2025-08-26T19:40:13.7288602Z  2025-08-26T19:40:13.7289420Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:40:13.7290707Z  repo = gh.get_repo(repo) 2025-08-26T19:40:13.7291397Z  return repo.get_issue(number=issue_num) 2025-08-26T19:40:13.7292055Z  2025-08-26T19:40:13.7292475Z  2025-08-26T19:40:13.7292924Z def get_potential_pr_author( 2025-08-26T19:40:13.7293778Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:40:13.7294631Z ) -> str: 2025-08-26T19:40:13.7295307Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:40:13.7296346Z  # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:40:13.7297331Z  # embedded in the tag name: ciflow// 2025-08-26T19:40:13.7298060Z  2025-08-26T19:40:13.7298530Z  gh = get_gh_client(github_token) 2025-08-26T19:40:13.7299158Z  2025-08-26T19:40:13.7299830Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:40:13.7300640Z  split_tag = ref_name.split("/") 2025-08-26T19:40:13.7301282Z  if ( 2025-08-26T19:40:13.7301784Z  len(split_tag) == 3 2025-08-26T19:40:13.7302430Z  and split_tag[0] == "ciflow" 2025-08-26T19:40:13.7303108Z  and split_tag[2].isnumeric() 2025-08-26T19:40:13.7303793Z  ): 2025-08-26T19:40:13.7304344Z  pr_number = split_tag[2] 2025-08-26T19:40:13.7305028Z  try: 2025-08-26T19:40:13.7305646Z  repository = gh.get_repo(repo) 2025-08-26T19:40:13.7306659Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:40:13.7307506Z  except Exception as e: 2025-08-26T19:40:13.7308238Z  raise Exception( # noqa: TRY002 2025-08-26T19:40:13.7309171Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:40:13.7310197Z  ) from e 2025-08-26T19:40:13.7310982Z  return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:40:13.7311968Z  # In all other cases, return the original input username 2025-08-26T19:40:13.7312783Z  return username 2025-08-26T19:40:13.7313354Z  2025-08-26T19:40:13.7313798Z  2025-08-26T19:40:13.7314364Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:40:13.7315096Z  """ 2025-08-26T19:40:13.7316014Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:40:13.7317110Z  """ 2025-08-26T19:40:13.7317871Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:40:13.7318785Z  2025-08-26T19:40:13.7319232Z  2025-08-26T19:40:13.7319843Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:40:13.7320538Z  try: 2025-08-26T19:40:13.7321088Z  data = yaml.safe_load(yaml_text) 2025-08-26T19:40:13.7321787Z  return data 2025-08-26T19:40:13.7322396Z  except yaml.YAMLError: 2025-08-26T19:40:13.7323100Z  log.exception("Error loading YAML") 2025-08-26T19:40:13.7323801Z  raise 2025-08-26T19:40:13.7324324Z  2025-08-26T19:40:13.7324765Z  2025-08-26T19:40:13.7325604Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:40:13.7326630Z  """ 2025-08-26T19:40:13.7327640Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:40:13.7328670Z  2025-08-26T19:40:13.7329402Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:40:13.7330577Z  and the text below is the list of opted in users. 2025-08-26T19:40:13.7331339Z  2025-08-26T19:40:13.7332117Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:40:13.7333074Z  """ 2025-08-26T19:40:13.7333695Z  rollout_state_parts = rollout_state.split("---") 2025-08-26T19:40:13.7334518Z  if len(rollout_state_parts) >= 2: 2025-08-26T19:40:13.7335497Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:40:13.7336440Z  else: 2025-08-26T19:40:13.7336979Z  return "", rollout_state 2025-08-26T19:40:13.7337627Z  2025-08-26T19:40:13.7338070Z  2025-08-26T19:40:13.7338601Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:40:13.7339293Z  """ 2025-08-26T19:40:13.7340123Z  Dictionary of users with a list of features they have opted into 2025-08-26T19:40:13.7341012Z  """ 2025-08-26T19:40:13.7341484Z  2025-08-26T19:40:13.7341916Z  2025-08-26T19:40:13.7342630Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:40:13.7343534Z  """ 2025-08-26T19:40:13.7344488Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-26T19:40:13.7345549Z  2025-08-26T19:40:13.7346560Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-26T19:40:13.7347825Z  - Example line: "@User1,lf,split_build" 2025-08-26T19:40:13.7348836Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:40:13.7349627Z  2025-08-26T19:40:13.7350136Z  2025-08-26T19:40:13.7350543Z  """ 2025-08-26T19:40:13.7351013Z  optins = UserOptins() 2025-08-26T19:40:13.7351654Z  for user in user_optin_text.split("\n"): 2025-08-26T19:40:13.7352364Z  user = user.strip("\r\n\t -") 2025-08-26T19:40:13.7353062Z  if not user or not user.startswith("@"): 2025-08-26T19:40:13.7353766Z  # Not a valid user. Skip 2025-08-26T19:40:13.7354393Z  continue 2025-08-26T19:40:13.7354959Z  2025-08-26T19:40:13.7355383Z  if user: 2025-08-26T19:40:13.7355959Z  usr_name = user.split(",")[0].strip("@") 2025-08-26T19:40:13.7356831Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:40:13.7357645Z  2025-08-26T19:40:13.7358070Z  return optins 2025-08-26T19:40:13.7358586Z  2025-08-26T19:40:13.7358983Z  2025-08-26T19:40:13.7359591Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:40:13.7360452Z  """ 2025-08-26T19:40:13.7360964Z  Check if the experiment name is valid. 2025-08-26T19:40:13.7361612Z  A valid name: 2025-08-26T19:40:13.7362459Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:40:13.7363647Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:40:13.7364543Z  - Cannot contain spaces 2025-08-26T19:40:13.7365141Z  """ 2025-08-26T19:40:13.7365572Z  2025-08-26T19:40:13.7366133Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:40:13.7367032Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:40:13.7367976Z  2025-08-26T19:40:13.7368611Z  if valid: 2025-08-26T19:40:13.7369141Z  return True 2025-08-26T19:40:13.7369670Z  2025-08-26T19:40:13.7370183Z  log.error( 2025-08-26T19:40:13.7372069Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-26T19:40:13.7374069Z  ) 2025-08-26T19:40:13.7374515Z  return False 2025-08-26T19:40:13.7375019Z  2025-08-26T19:40:13.7375424Z  2025-08-26T19:40:13.7376065Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:40:13.7376872Z  """ 2025-08-26T19:40:13.7377636Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:40:13.7378548Z  """ 2025-08-26T19:40:13.7378998Z  try: 2025-08-26T19:40:13.7379462Z  if settings_text: 2025-08-26T19:40:13.7380492Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-26T19:40:13.7381523Z  # for easy reading 2025-08-26T19:40:13.7382586Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:40:13.7383727Z  # the backtick character in shell commands. 2025-08-26T19:40:13.7384523Z  backtick = chr(96) # backtick character 2025-08-26T19:40:13.7385370Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:40:13.7386212Z  settings = load_yaml(settings_text) 2025-08-26T19:40:13.7386819Z  2025-08-26T19:40:13.7387518Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:40:13.7388534Z  experiments = {} 2025-08-26T19:40:13.7389077Z  2025-08-26T19:40:13.7389829Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:40:13.7390738Z  if not is_valid_experiment_name(exp_name): 2025-08-26T19:40:13.7392062Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-26T19:40:13.7393348Z  continue 2025-08-26T19:40:13.7393897Z  2025-08-26T19:40:13.7394326Z  valid_settings = {} 2025-08-26T19:40:13.7394942Z  for setting in exp_settings: 2025-08-26T19:40:13.7395615Z  if setting not in Experiment._fields: 2025-08-26T19:40:13.7396271Z  log.warning( 2025-08-26T19:40:13.7397166Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:40:13.7398007Z  ) 2025-08-26T19:40:13.7398535Z  else: 2025-08-26T19:40:13.7399214Z  valid_settings[setting] = exp_settings[setting] 2025-08-26T19:40:13.7399977Z  2025-08-26T19:40:13.7400527Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:40:13.7401286Z  return Settings(experiments) 2025-08-26T19:40:13.7401878Z  2025-08-26T19:40:13.7402289Z  except Exception: 2025-08-26T19:40:13.7402888Z  log.exception("Failed to parse settings") 2025-08-26T19:40:13.7403516Z  2025-08-26T19:40:13.7403918Z  return Settings() 2025-08-26T19:40:13.7404422Z  2025-08-26T19:40:13.7404834Z  2025-08-26T19:40:13.7405498Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:40:13.7406181Z  """ 2025-08-26T19:40:13.7406711Z  Parse settings, if any, from the rollout state. 2025-08-26T19:40:13.7407356Z  2025-08-26T19:40:13.7407979Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:40:13.7408891Z  and the text below is the list of opted in users. 2025-08-26T19:40:13.7409530Z  2025-08-26T19:40:13.7410330Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:40:13.7411182Z  """ 2025-08-26T19:40:13.7411852Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:40:13.7412763Z  return parse_settings_from_text(settings_text) 2025-08-26T19:40:13.7413408Z  2025-08-26T19:40:13.7413789Z  2025-08-26T19:40:13.7414313Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:40:13.7414984Z  """ 2025-08-26T19:40:13.7415456Z  Parse users from the rollout state. 2025-08-26T19:40:13.7416048Z  2025-08-26T19:40:13.7416427Z  """ 2025-08-26T19:40:13.7417079Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:40:13.7417971Z  return parse_user_opt_in_from_text(users_text) 2025-08-26T19:40:13.7418613Z  2025-08-26T19:40:13.7418998Z  2025-08-26T19:40:13.7419804Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:40:13.7420693Z  """ 2025-08-26T19:40:13.7421204Z  Check if a user is opted into an experiment 2025-08-26T19:40:13.7421832Z  """ 2025-08-26T19:40:13.7422380Z  return experiment_name in user_optins.get(user, []) 2025-08-26T19:40:13.7423066Z  2025-08-26T19:40:13.7423575Z  2025-08-26T19:40:13.7424304Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:40:13.7425199Z  """ 2025-08-26T19:40:13.7425753Z  Check if a user explicitly opted out of an experiment 2025-08-26T19:40:13.7426488Z  """ 2025-08-26T19:40:13.7427098Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:40:13.7427956Z  experiment_optout = "-" + experiment_name 2025-08-26T19:40:13.7428730Z  if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:40:13.7429437Z  return False 2025-08-26T19:40:13.7430024Z  2025-08-26T19:40:13.7430552Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:40:13.7431265Z  log.warning( 2025-08-26T19:40:13.7432253Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:40:13.7433301Z  ) 2025-08-26T19:40:13.7433746Z  2025-08-26T19:40:13.7434169Z  return True 2025-08-26T19:40:13.7434667Z  2025-08-26T19:40:13.7435070Z  2025-08-26T19:40:13.7435508Z def get_runner_prefix( 2025-08-26T19:40:13.7436073Z  rollout_state: str, 2025-08-26T19:40:13.7436688Z  workflow_requestors: Iterable[str], 2025-08-26T19:40:13.7437332Z  branch: str, 2025-08-26T19:40:13.7438018Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:40:13.7438889Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:40:13.7439636Z  is_canary: bool = False, 2025-08-26T19:40:13.7440309Z ) -> str: 2025-08-26T19:40:13.7440858Z  settings = parse_settings(rollout_state) 2025-08-26T19:40:13.7441599Z  user_optins = parse_users(rollout_state) 2025-08-26T19:40:13.7442255Z  2025-08-26T19:40:13.7442813Z  fleet_prefix = "" 2025-08-26T19:40:13.7443373Z  prefixes = [] 2025-08-26T19:40:13.7444218Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:40:13.7445428Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:40:13.7446332Z  log.info( 2025-08-26T19:40:13.7447223Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:40:13.7448166Z  ) 2025-08-26T19:40:13.7448673Z  continue 2025-08-26T19:40:13.7449188Z  2025-08-26T19:40:13.7449630Z  if opt_out_experiments: 2025-08-26T19:40:13.7450422Z  if experiment_name in opt_out_experiments: 2025-08-26T19:40:13.7451237Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:40:13.7451981Z  log.info( 2025-08-26T19:40:13.7453177Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:40:13.7454420Z  ) 2025-08-26T19:40:13.7454945Z  continue 2025-08-26T19:40:13.7455488Z  2025-08-26T19:40:13.7455947Z  if eligible_experiments: 2025-08-26T19:40:13.7456676Z  if experiment_name not in eligible_experiments: 2025-08-26T19:40:13.7457495Z  exp_list = ", ".join(eligible_experiments) 2025-08-26T19:40:13.7458191Z  log.info( 2025-08-26T19:40:13.7459219Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:40:13.7460414Z  ) 2025-08-26T19:40:13.7461063Z  continue 2025-08-26T19:40:13.7461693Z  elif not experiment_settings.default: 2025-08-26T19:40:13.7462349Z  log.info( 2025-08-26T19:40:13.7463215Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:40:13.7464136Z  ) 2025-08-26T19:40:13.7464628Z  continue 2025-08-26T19:40:13.7465147Z  2025-08-26T19:40:13.7465718Z  # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:40:13.7466496Z  opted_out_users = [ 2025-08-26T19:40:13.7467076Z  requestor 2025-08-26T19:40:13.7467687Z  for requestor in workflow_requestors 2025-08-26T19:40:13.7468535Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:40:13.7469334Z  ] 2025-08-26T19:40:13.7469884Z  2025-08-26T19:40:13.7470330Z  if opted_out_users: 2025-08-26T19:40:13.7470944Z  log.info( 2025-08-26T19:40:13.7471756Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:40:13.7472637Z  ) 2025-08-26T19:40:13.7473122Z  continue 2025-08-26T19:40:13.7473636Z  2025-08-26T19:40:13.7474201Z  # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:40:13.7474975Z  opted_in_users = [ 2025-08-26T19:40:13.7475574Z  requestor 2025-08-26T19:40:13.7476182Z  for requestor in workflow_requestors 2025-08-26T19:40:13.7477033Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:40:13.7477813Z  ] 2025-08-26T19:40:13.7478270Z  2025-08-26T19:40:13.7478701Z  enabled = False 2025-08-26T19:40:13.7479287Z  if opted_in_users: 2025-08-26T19:40:13.7480082Z  log.info( 2025-08-26T19:40:13.7480888Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:40:13.7481773Z  ) 2025-08-26T19:40:13.7482266Z  enabled = True 2025-08-26T19:40:13.7482824Z  2025-08-26T19:40:13.7483318Z  elif experiment_settings.rollout_perc: 2025-08-26T19:40:13.7484381Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:40:13.7485579Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:40:13.7486395Z  log.info( 2025-08-26T19:40:13.7487526Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:40:13.7488684Z  ) 2025-08-26T19:40:13.7489232Z  enabled = True 2025-08-26T19:40:13.7489910Z  2025-08-26T19:40:13.7490335Z  if enabled: 2025-08-26T19:40:13.7490902Z  label = experiment_name 2025-08-26T19:40:13.7491607Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:40:13.7492657Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:40:13.7493762Z  # - If it's enabled, then we always list it's prefix first 2025-08-26T19:40:13.7494737Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:40:13.7495571Z  if is_canary: 2025-08-26T19:40:13.7496211Z  label += CANARY_FLEET_SUFFIX 2025-08-26T19:40:13.7496897Z  fleet_prefix = label 2025-08-26T19:40:13.7497507Z  else: 2025-08-26T19:40:13.7498205Z  prefixes.append(label) 2025-08-26T19:40:13.7498824Z  2025-08-26T19:40:13.7499265Z  if len(prefixes) > 1: 2025-08-26T19:40:13.7499945Z  log.error( 2025-08-26T19:40:13.7501283Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-26T19:40:13.7502698Z  ) 2025-08-26T19:40:13.7503189Z  prefixes = prefixes[:1] 2025-08-26T19:40:13.7503790Z  2025-08-26T19:40:13.7504236Z  # Fleet always comes first 2025-08-26T19:40:13.7504853Z  if fleet_prefix: 2025-08-26T19:40:13.7505444Z  prefixes.insert(0, fleet_prefix) 2025-08-26T19:40:13.7506072Z  2025-08-26T19:40:13.7506623Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:40:13.7507330Z  2025-08-26T19:40:13.7507746Z  2025-08-26T19:40:13.7508538Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:40:13.7509517Z  """ 2025-08-26T19:40:13.7510355Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:40:13.7511246Z  2025-08-26T19:40:13.7511963Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:40:13.7512845Z  """ 2025-08-26T19:40:13.7513343Z  gh = get_gh_client(github_token) 2025-08-26T19:40:13.7514034Z  issue = get_issue(gh, repo, issue_num) 2025-08-26T19:40:13.7514856Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:40:13.7515604Z  2025-08-26T19:40:13.7516010Z  2025-08-26T19:40:13.7516752Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:40:13.7517833Z  for _ in range(num_retries): 2025-08-26T19:40:13.7518445Z  try: 2025-08-26T19:40:13.7518993Z  req = Request(url=url, headers=headers) 2025-08-26T19:40:13.7519928Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:40:13.7520749Z  return json.loads(content) 2025-08-26T19:40:13.7521414Z  except Exception as e: 2025-08-26T19:40:13.7522120Z  log.warning(f"Could not download {url}: {e}") 2025-08-26T19:40:13.7522806Z  2025-08-26T19:40:13.7523519Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:40:13.7524428Z  return {} 2025-08-26T19:40:13.7524911Z  2025-08-26T19:40:13.7525330Z  2025-08-26T19:40:13.7525831Z @cache 2025-08-26T19:40:13.7526635Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:40:13.7527610Z  """ 2025-08-26T19:40:13.7528133Z  Dynamically get PR information 2025-08-26T19:40:13.7528745Z  """ 2025-08-26T19:40:13.7529376Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:40:13.7530247Z  headers = { 2025-08-26T19:40:13.7530853Z  "Accept": "application/vnd.github.v3+json", 2025-08-26T19:40:13.7531629Z  "Authorization": f"token {github_token}", 2025-08-26T19:40:13.7532288Z  } 2025-08-26T19:40:13.7532845Z  json_response: dict[str, Any] = download_json( 2025-08-26T19:40:13.7533610Z  url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:40:13.7534304Z  headers=headers, 2025-08-26T19:40:13.7534855Z  ) 2025-08-26T19:40:13.7535293Z  2025-08-26T19:40:13.7535734Z  if not json_response: 2025-08-26T19:40:13.7536484Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:40:13.7537408Z  return {} 2025-08-26T19:40:13.7537920Z  2025-08-26T19:40:13.7538358Z  return json_response 2025-08-26T19:40:13.7538908Z  2025-08-26T19:40:13.7539319Z  2025-08-26T19:40:13.7540154Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:40:13.7541082Z  """ 2025-08-26T19:40:13.7541753Z  Dynamically get the latest list of labels from the pull request 2025-08-26T19:40:13.7542575Z  """ 2025-08-26T19:40:13.7543196Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:40:13.7543959Z  return { 2025-08-26T19:40:13.7544747Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:40:13.7545608Z  } 2025-08-26T19:40:13.7546016Z  2025-08-26T19:40:13.7546412Z  2025-08-26T19:40:13.7546824Z def main() -> None: 2025-08-26T19:40:13.7547334Z  args = parse_args() 2025-08-26T19:40:13.7547850Z  2025-08-26T19:40:13.7548335Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:40:13.7548958Z  2025-08-26T19:40:13.7549385Z  # Check if the PR is opt-out 2025-08-26T19:40:13.7550063Z  if args.pr_number: 2025-08-26T19:40:13.7550872Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:40:13.7551827Z  if OPT_OUT_LABEL in labels: 2025-08-26T19:40:13.7552448Z  log.info( 2025-08-26T19:40:13.7553337Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:40:13.7554290Z  ) 2025-08-26T19:40:13.7554979Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:40:13.7555772Z  sys.exit() 2025-08-26T19:40:13.7556398Z  2025-08-26T19:40:13.7556789Z  try: 2025-08-26T19:40:13.7557317Z  rollout_state = get_rollout_state_from_issue( 2025-08-26T19:40:13.7558165Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:40:13.7558906Z  ) 2025-08-26T19:40:13.7559329Z  2025-08-26T19:40:13.7559880Z  username = get_potential_pr_author( 2025-08-26T19:40:13.7560514Z  args.github_token, 2025-08-26T19:40:13.7561088Z  args.github_repo, 2025-08-26T19:40:13.7561663Z  args.github_actor, 2025-08-26T19:40:13.7562251Z  args.github_ref_type, 2025-08-26T19:40:13.7562849Z  args.github_branch, 2025-08-26T19:40:13.7563401Z  ) 2025-08-26T19:40:13.7563818Z  2025-08-26T19:40:13.7564369Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:40:13.7565071Z  2025-08-26T19:40:13.7565543Z  runner_label_prefix = get_runner_prefix( 2025-08-26T19:40:13.7566180Z  rollout_state, 2025-08-26T19:40:13.7566781Z  (args.github_issue_owner, username), 2025-08-26T19:40:13.7567422Z  args.github_branch, 2025-08-26T19:40:13.7568024Z  args.eligible_experiments, 2025-08-26T19:40:13.7568665Z  args.opt_out_experiments, 2025-08-26T19:40:13.7569256Z  is_canary, 2025-08-26T19:40:13.7569853Z  ) 2025-08-26T19:40:13.7570274Z  2025-08-26T19:40:13.7570697Z  except Exception as e: 2025-08-26T19:40:13.7571251Z  log.error( 2025-08-26T19:40:13.7572085Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:40:13.7573101Z  ) 2025-08-26T19:40:13.7573527Z  2025-08-26T19:40:13.7574140Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:40:13.7574906Z  2025-08-26T19:40:13.7575293Z  2025-08-26T19:40:13.7575706Z if __name__ == "__main__": 2025-08-26T19:40:13.7576247Z  main() 2025-08-26T19:40:13.7576675Z  2025-08-26T19:40:13.7577064Z EOF 2025-08-26T19:40:13.7577464Z  2025-08-26T19:40:13.7577884Z cat runner_determinator.py 2025-08-26T19:40:13.7833080Z shell: /usr/bin/bash -e {0} 2025-08-26T19:40:13.7834022Z env: 2025-08-26T19:40:13.7834923Z GITHUB_TOKEN: *** 2025-08-26T19:40:13.7835455Z ISSUE_NUMBER: 5132 2025-08-26T19:40:13.7835999Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:40:13.7836620Z ISSUE_OWNER: 2025-08-26T19:40:13.7837100Z CHECK_EXPERIMENTS: 2025-08-26T19:40:13.7837634Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:40:13.7838157Z PR_NUMBER: 2025-08-26T19:40:13.7838691Z ##[endgroup] 2025-08-26T19:40:13.8054512Z # flake8: noqa: G004 2025-08-26T19:40:13.8054900Z 2025-08-26T19:40:13.8055433Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:40:13.8056638Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:40:13.8057625Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:40:13.8058183Z 2025-08-26T19:40:13.8058373Z """ 2025-08-26T19:40:13.8059089Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:40:13.8060444Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:40:13.8061582Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:40:13.8062603Z of which runners should be used to run which job. 2025-08-26T19:40:13.8063117Z 2025-08-26T19:40:13.8063594Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:40:13.8064917Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:40:13.8066030Z settings are considered to be empty with only the second part, the user 2025-08-26T19:40:13.8066906Z list, defined. 2025-08-26T19:40:13.8067179Z 2025-08-26T19:40:13.8067633Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:40:13.8068829Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:40:13.8070122Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:40:13.8070703Z 2025-08-26T19:40:13.8071165Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:40:13.8072267Z The user list is also a comma separated list of additional features or 2025-08-26T19:40:13.8073186Z experiments which the user could be opted in to. 2025-08-26T19:40:13.8073693Z 2025-08-26T19:40:13.8073938Z The user list has the following rules: 2025-08-26T19:40:13.8074368Z 2025-08-26T19:40:13.8074768Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:40:13.8075818Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:40:13.8076707Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:40:13.8077166Z 2025-08-26T19:40:13.8077352Z Example config: 2025-08-26T19:40:13.8077871Z # A list of experiments that can be opted into. 2025-08-26T19:40:13.8078640Z # This defines the behavior they'll induce when opted into. 2025-08-26T19:40:13.8079367Z # Expected syntax is: 2025-08-26T19:40:13.8080739Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:40:13.8081904Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:40:13.8082632Z 2025-08-26T19:40:13.8082832Z experiments: 2025-08-26T19:40:13.8083264Z lf: 2025-08-26T19:40:13.8083688Z rollout_percent: 25 2025-08-26T19:40:13.8084374Z all_branches: false 2025-08-26T19:40:13.8084887Z default: true 2025-08-26T19:40:13.8085339Z --- 2025-08-26T19:40:13.8085573Z 2025-08-26T19:40:13.8085755Z # Opt-ins: 2025-08-26T19:40:13.8086410Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:40:13.8087431Z # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:40:13.8088343Z # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:40:13.8089104Z # Experiments should be from the above list. 2025-08-26T19:40:13.8089544Z 2025-08-26T19:40:13.8089955Z @User1,-lf,split_build 2025-08-26T19:40:13.8090463Z @User2,lf 2025-08-26T19:40:13.8090896Z @User3,split_build 2025-08-26T19:40:13.8091355Z """ 2025-08-26T19:40:13.8091576Z 2025-08-26T19:40:13.8091762Z import json 2025-08-26T19:40:13.8092216Z import logging 2025-08-26T19:40:13.8092645Z import os 2025-08-26T19:40:13.8093051Z import random 2025-08-26T19:40:13.8093478Z import re 2025-08-26T19:40:13.8093889Z import sys 2025-08-26T19:40:13.8094346Z from argparse import ArgumentParser 2025-08-26T19:40:13.8094956Z from collections.abc import Iterable 2025-08-26T19:40:13.8095542Z from functools import cache 2025-08-26T19:40:13.8096048Z from logging import LogRecord 2025-08-26T19:40:13.8096568Z from typing import Any, NamedTuple 2025-08-26T19:40:13.8097145Z from urllib.request import Request, urlopen 2025-08-26T19:40:13.8097554Z 2025-08-26T19:40:13.8097727Z import yaml 2025-08-26T19:40:13.8098144Z from github import Auth, Github 2025-08-26T19:40:13.8098658Z from github.Issue import Issue 2025-08-26T19:40:13.8098991Z 2025-08-26T19:40:13.8098999Z 2025-08-26T19:40:13.8099226Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:40:13.8100138Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:40:13.8101082Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:40:13.8101710Z 2025-08-26T19:40:13.8101951Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:40:13.8102704Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:40:13.8103257Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:40:13.8103843Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:40:13.8104234Z 2025-08-26T19:40:13.8104439Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:40:13.8104802Z 2025-08-26T19:40:13.8105001Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:40:13.8105492Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:40:13.8105795Z 2025-08-26T19:40:13.8105808Z 2025-08-26T19:40:13.8106005Z class Experiment(NamedTuple): 2025-08-26T19:40:13.8106507Z rollout_perc: float = ( 2025-08-26T19:40:13.8107197Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:40:13.8107949Z ) 2025-08-26T19:40:13.8108344Z all_branches: bool = ( 2025-08-26T19:40:13.8109021Z False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:40:13.8109976Z ) 2025-08-26T19:40:13.8110395Z default: bool = ( 2025-08-26T19:40:13.8111009Z True # If True, the experiment is enabled by default for all queries 2025-08-26T19:40:13.8111720Z ) 2025-08-26T19:40:13.8111924Z 2025-08-26T19:40:13.8112112Z # Add more fields as needed 2025-08-26T19:40:13.8112443Z 2025-08-26T19:40:13.8112450Z 2025-08-26T19:40:13.8112643Z class Settings(NamedTuple): 2025-08-26T19:40:13.8113115Z """ 2025-08-26T19:40:13.8113591Z Settings for the experiments that can be opted into. 2025-08-26T19:40:13.8114222Z """ 2025-08-26T19:40:13.8114428Z 2025-08-26T19:40:13.8114646Z experiments: dict[str, Experiment] = {} 2025-08-26T19:40:13.8115041Z 2025-08-26T19:40:13.8115056Z 2025-08-26T19:40:13.8115276Z class ColorFormatter(logging.Formatter): 2025-08-26T19:40:13.8115918Z """Color codes the log messages based on the log level""" 2025-08-26T19:40:13.8116380Z 2025-08-26T19:40:13.8116542Z COLORS = { 2025-08-26T19:40:13.8116949Z "WARNING": "\033[33m", # Yellow 2025-08-26T19:40:13.8117610Z "ERROR": "\033[31m", # Red 2025-08-26T19:40:13.8118112Z "CRITICAL": "\033[31m", # Red 2025-08-26T19:40:13.8118618Z "INFO": "\033[0m", # Reset 2025-08-26T19:40:13.8119111Z "DEBUG": "\033[0m", # Reset 2025-08-26T19:40:13.8119605Z } 2025-08-26T19:40:13.8120013Z 2025-08-26T19:40:13.8120247Z def format(self, record: LogRecord) -> str: 2025-08-26T19:40:13.8121033Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:40:13.8121842Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:40:13.8122435Z return super().format(record) 2025-08-26T19:40:13.8122783Z 2025-08-26T19:40:13.8122790Z 2025-08-26T19:40:13.8122987Z handler = logging.StreamHandler() 2025-08-26T19:40:13.8123729Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:40:13.8124323Z 2025-08-26T19:40:13.8124570Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:40:13.8125193Z log.addHandler(handler) 2025-08-26T19:40:13.8125641Z log.setLevel(logging.INFO) 2025-08-26T19:40:13.8125939Z 2025-08-26T19:40:13.8125946Z 2025-08-26T19:40:13.8126195Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:40:13.8126774Z """ 2025-08-26T19:40:13.8127317Z Defines outputs of the github action that invokes this script 2025-08-26T19:40:13.8127976Z """ 2025-08-26T19:40:13.8128342Z if not GITHUB_OUTPUT: 2025-08-26T19:40:13.8129476Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-26T19:40:13.8130861Z log.warning( 2025-08-26T19:40:13.8131757Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-26T19:40:13.8132739Z ) 2025-08-26T19:40:13.8142534Z print(f"::set-output name={key}::{value}") 2025-08-26T19:40:13.8143180Z return 2025-08-26T19:40:13.8143413Z 2025-08-26T19:40:13.8143820Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:40:13.8144455Z log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:40:13.8145052Z f.write(f"{key}={value}\n") 2025-08-26T19:40:13.8145395Z 2025-08-26T19:40:13.8145402Z 2025-08-26T19:40:13.8145716Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:40:13.8146378Z return frozenset( 2025-08-26T19:40:13.8147003Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:40:13.8147718Z ) 2025-08-26T19:40:13.8147915Z 2025-08-26T19:40:13.8147921Z 2025-08-26T19:40:13.8148103Z def parse_args() -> Any: 2025-08-26T19:40:13.8148669Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:40:13.8149579Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:40:13.8150757Z parser.add_argument( 2025-08-26T19:40:13.8151227Z "--github-issue-repo", 2025-08-26T19:40:13.8151714Z type=str, 2025-08-26T19:40:13.8152128Z required=False, 2025-08-26T19:40:13.8152587Z default="pytorch/test-infra", 2025-08-26T19:40:13.8153145Z help="GitHub repo to get the issue", 2025-08-26T19:40:13.8153675Z ) 2025-08-26T19:40:13.8154054Z parser.add_argument( 2025-08-26T19:40:13.8154503Z "--github-repo", 2025-08-26T19:40:13.8154937Z type=str, 2025-08-26T19:40:13.8155334Z required=True, 2025-08-26T19:40:13.8155813Z help="GitHub repo where CI is running", 2025-08-26T19:40:13.8156352Z ) 2025-08-26T19:40:13.8156732Z parser.add_argument( 2025-08-26T19:40:13.8157362Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:40:13.8158050Z ) 2025-08-26T19:40:13.8158421Z parser.add_argument( 2025-08-26T19:40:13.8159223Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:40:13.8160216Z ) 2025-08-26T19:40:13.8160660Z parser.add_argument( 2025-08-26T19:40:13.8161551Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:40:13.8162278Z ) 2025-08-26T19:40:13.8162657Z parser.add_argument( 2025-08-26T19:40:13.8163345Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:40:13.8164088Z ) 2025-08-26T19:40:13.8164467Z parser.add_argument( 2025-08-26T19:40:13.8164938Z "--github-ref-type", 2025-08-26T19:40:13.8215813Z type=str, 2025-08-26T19:40:13.8216781Z required=True, 2025-08-26T19:40:13.8217747Z help="Current GitHub ref type, branch or tag", 2025-08-26T19:40:13.8218568Z ) 2025-08-26T19:40:13.8218964Z parser.add_argument( 2025-08-26T19:40:13.8219442Z "--eligible-experiments", 2025-08-26T19:40:13.8220194Z type=_str_comma_separated_to_set, 2025-08-26T19:40:13.8220741Z required=False, 2025-08-26T19:40:13.8221192Z default="", 2025-08-26T19:40:13.8222143Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:40:13.8223174Z ) 2025-08-26T19:40:13.8223561Z parser.add_argument( 2025-08-26T19:40:13.8224037Z "--opt-out-experiments", 2025-08-26T19:40:13.8224579Z type=_str_comma_separated_to_set, 2025-08-26T19:40:13.8225131Z required=False, 2025-08-26T19:40:13.8225568Z default="", 2025-08-26T19:40:13.8225972Z help=( 2025-08-26T19:40:13.8226691Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:40:13.8227915Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:40:13.8228763Z ), 2025-08-26T19:40:13.8229117Z ) 2025-08-26T19:40:13.8229578Z parser.add_argument( 2025-08-26T19:40:13.8230260Z "--pr-number", 2025-08-26T19:40:13.8230677Z type=str, 2025-08-26T19:40:13.8231076Z required=False, 2025-08-26T19:40:13.8231543Z default="", 2025-08-26T19:40:13.8232391Z help="the optional PR number where this is run", 2025-08-26T19:40:13.8233064Z ) 2025-08-26T19:40:13.8233260Z 2025-08-26T19:40:13.8233445Z return parser.parse_args() 2025-08-26T19:40:13.8233757Z 2025-08-26T19:40:13.8233764Z 2025-08-26T19:40:13.8234188Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:40:13.8234952Z auth = Auth.Token(github_token) 2025-08-26T19:40:13.8235479Z return Github(auth=auth) 2025-08-26T19:40:13.8235783Z 2025-08-26T19:40:13.8235790Z 2025-08-26T19:40:13.8236256Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:40:13.8237069Z repo = gh.get_repo(repo) 2025-08-26T19:40:13.8237571Z return repo.get_issue(number=issue_num) 2025-08-26T19:40:13.8237935Z 2025-08-26T19:40:13.8237942Z 2025-08-26T19:40:13.8238127Z def get_potential_pr_author( 2025-08-26T19:40:13.8238797Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:40:13.8239490Z ) -> str: 2025-08-26T19:40:13.8240236Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:40:13.8241392Z # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:40:13.8242493Z # embedded in the tag name: ciflow// 2025-08-26T19:40:13.8242928Z 2025-08-26T19:40:13.8243117Z gh = get_gh_client(github_token) 2025-08-26T19:40:13.8243451Z 2025-08-26T19:40:13.8243721Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:40:13.8244359Z split_tag = ref_name.split("/") 2025-08-26T19:40:13.8244864Z if ( 2025-08-26T19:40:13.8245252Z len(split_tag) == 3 2025-08-26T19:40:13.8245737Z and split_tag[0] == "ciflow" 2025-08-26T19:40:13.8246263Z and split_tag[2].isnumeric() 2025-08-26T19:40:13.8246756Z ): 2025-08-26T19:40:13.8247140Z pr_number = split_tag[2] 2025-08-26T19:40:13.8247836Z try: 2025-08-26T19:40:13.8248267Z repository = gh.get_repo(repo) 2025-08-26T19:40:13.8248883Z pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:40:13.8249476Z except Exception as e: 2025-08-26T19:40:13.8250232Z raise Exception( # noqa: TRY002 2025-08-26T19:40:13.8250903Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:40:13.8251539Z ) from e 2025-08-26T19:40:13.8252076Z return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:40:13.8252761Z # In all other cases, return the original input username 2025-08-26T19:40:13.8253491Z return username 2025-08-26T19:40:13.8253741Z 2025-08-26T19:40:13.8253748Z 2025-08-26T19:40:13.8253967Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:40:13.8254500Z """ 2025-08-26T19:40:13.8255324Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:40:13.8256124Z """ 2025-08-26T19:40:13.8256678Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:40:13.8257205Z 2025-08-26T19:40:13.8257211Z 2025-08-26T19:40:13.8257406Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:40:13.8257901Z try: 2025-08-26T19:40:13.8258286Z data = yaml.safe_load(yaml_text) 2025-08-26T19:40:13.8258790Z return data 2025-08-26T19:40:13.8259199Z except yaml.YAMLError: 2025-08-26T19:40:13.8259850Z log.exception("Error loading YAML") 2025-08-26T19:40:13.8260382Z raise 2025-08-26T19:40:13.8260601Z 2025-08-26T19:40:13.8260608Z 2025-08-26T19:40:13.8261031Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:40:13.8261768Z """ 2025-08-26T19:40:13.8262383Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:40:13.8263001Z 2025-08-26T19:40:13.8263501Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:40:13.8264259Z and the text below is the list of opted in users. 2025-08-26T19:40:13.8264664Z 2025-08-26T19:40:13.8265035Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:40:13.8265737Z """ 2025-08-26T19:40:13.8266174Z rollout_state_parts = rollout_state.split("---") 2025-08-26T19:40:13.8266786Z if len(rollout_state_parts) >= 2: 2025-08-26T19:40:13.8267385Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:40:13.8267971Z else: 2025-08-26T19:40:13.8268351Z return "", rollout_state 2025-08-26T19:40:13.8268664Z 2025-08-26T19:40:13.8268671Z 2025-08-26T19:40:13.8268867Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:40:13.8269373Z """ 2025-08-26T19:40:13.8270115Z Dictionary of users with a list of features they have opted into 2025-08-26T19:40:13.8270776Z """ 2025-08-26T19:40:13.8270986Z 2025-08-26T19:40:13.8270992Z 2025-08-26T19:40:13.8271335Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:40:13.8271984Z """ 2025-08-26T19:40:13.8272691Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-26T19:40:13.8273377Z 2025-08-26T19:40:13.8273992Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-26T19:40:13.8274974Z - Example line: "@User1,lf,split_build" 2025-08-26T19:40:13.8275649Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:40:13.8276132Z 2025-08-26T19:40:13.8276138Z 2025-08-26T19:40:13.8276294Z """ 2025-08-26T19:40:13.8276655Z optins = UserOptins() 2025-08-26T19:40:13.8277140Z for user in user_optin_text.split("\n"): 2025-08-26T19:40:13.8277694Z user = user.strip("\r\n\t -") 2025-08-26T19:40:13.8278226Z if not user or not user.startswith("@"): 2025-08-26T19:40:13.8278926Z # Not a valid user. Skip 2025-08-26T19:40:13.8279406Z continue 2025-08-26T19:40:13.8279650Z 2025-08-26T19:40:13.8280085Z if user: 2025-08-26T19:40:13.8280548Z usr_name = user.split(",")[0].strip("@") 2025-08-26T19:40:13.8281248Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:40:13.8281744Z 2025-08-26T19:40:13.8281906Z return optins 2025-08-26T19:40:13.8282147Z 2025-08-26T19:40:13.8282153Z 2025-08-26T19:40:13.8282436Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:40:13.8283034Z """ 2025-08-26T19:40:13.8283416Z Check if the experiment name is valid. 2025-08-26T19:40:13.8283933Z A valid name: 2025-08-26T19:40:13.8284557Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:40:13.8285522Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:40:13.8286252Z - Cannot contain spaces 2025-08-26T19:40:13.8286719Z """ 2025-08-26T19:40:13.8286915Z 2025-08-26T19:40:13.8287179Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:40:13.8287875Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:40:13.8288319Z 2025-08-26T19:40:13.8288489Z if valid: 2025-08-26T19:40:13.8288857Z return True 2025-08-26T19:40:13.8289098Z 2025-08-26T19:40:13.8289260Z log.error( 2025-08-26T19:40:13.8291132Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-26T19:40:13.8292714Z ) 2025-08-26T19:40:13.8293078Z return False 2025-08-26T19:40:13.8293310Z 2025-08-26T19:40:13.8293317Z 2025-08-26T19:40:13.8293621Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:40:13.8294240Z """ 2025-08-26T19:40:13.8294983Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:40:13.8295715Z """ 2025-08-26T19:40:13.8296060Z try: 2025-08-26T19:40:13.8296432Z if settings_text: 2025-08-26T19:40:13.8297161Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-26T19:40:13.8297944Z # for easy reading 2025-08-26T19:40:13.8298730Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:40:13.8299608Z # the backtick character in shell commands. 2025-08-26T19:40:13.8300416Z backtick = chr(96) # backtick character 2025-08-26T19:40:13.8301079Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:40:13.8301747Z settings = load_yaml(settings_text) 2025-08-26T19:40:13.8302119Z 2025-08-26T19:40:13.8302541Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:40:13.8303291Z experiments = {} 2025-08-26T19:40:13.8303582Z 2025-08-26T19:40:13.8303966Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:40:13.8304720Z if not is_valid_experiment_name(exp_name): 2025-08-26T19:40:13.8305825Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-26T19:40:13.8306864Z continue 2025-08-26T19:40:13.8307153Z 2025-08-26T19:40:13.8307338Z valid_settings = {} 2025-08-26T19:40:13.8307860Z for setting in exp_settings: 2025-08-26T19:40:13.8308416Z if setting not in Experiment._fields: 2025-08-26T19:40:13.8308967Z log.warning( 2025-08-26T19:40:13.8309881Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:40:13.8310853Z ) 2025-08-26T19:40:13.8311281Z else: 2025-08-26T19:40:13.8311793Z valid_settings[setting] = exp_settings[setting] 2025-08-26T19:40:13.8312214Z 2025-08-26T19:40:13.8312488Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:40:13.8313105Z return Settings(experiments) 2025-08-26T19:40:13.8313453Z 2025-08-26T19:40:13.8313628Z except Exception: 2025-08-26T19:40:13.8314092Z log.exception("Failed to parse settings") 2025-08-26T19:40:13.8314485Z 2025-08-26T19:40:13.8314655Z return Settings() 2025-08-26T19:40:13.8314905Z 2025-08-26T19:40:13.8314911Z 2025-08-26T19:40:13.8315162Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:40:13.8315726Z """ 2025-08-26T19:40:13.8316151Z Parse settings, if any, from the rollout state. 2025-08-26T19:40:13.8316548Z 2025-08-26T19:40:13.8316891Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:40:13.8317650Z and the text below is the list of opted in users. 2025-08-26T19:40:13.8318044Z 2025-08-26T19:40:13.8318456Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:40:13.8319180Z """ 2025-08-26T19:40:13.8319951Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:40:13.8320716Z return parse_settings_from_text(settings_text) 2025-08-26T19:40:13.8321109Z 2025-08-26T19:40:13.8321116Z 2025-08-26T19:40:13.8321367Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:40:13.8321915Z """ 2025-08-26T19:40:13.8322297Z Parse users from the rollout state. 2025-08-26T19:40:13.8322641Z 2025-08-26T19:40:13.8322793Z """ 2025-08-26T19:40:13.8323316Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:40:13.8324069Z return parse_user_opt_in_from_text(users_text) 2025-08-26T19:40:13.8324477Z 2025-08-26T19:40:13.8324485Z 2025-08-26T19:40:13.8325029Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:40:13.8325784Z """ 2025-08-26T19:40:13.8326191Z Check if a user is opted into an experiment 2025-08-26T19:40:13.8326720Z """ 2025-08-26T19:40:13.8327158Z return experiment_name in user_optins.get(user, []) 2025-08-26T19:40:13.8327611Z 2025-08-26T19:40:13.8327618Z 2025-08-26T19:40:13.8328030Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:40:13.8328769Z """ 2025-08-26T19:40:13.8329215Z Check if a user explicitly opted out of an experiment 2025-08-26T19:40:13.8329986Z """ 2025-08-26T19:40:13.8330496Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:40:13.8331173Z experiment_optout = "-" + experiment_name 2025-08-26T19:40:13.8331792Z if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:40:13.8332385Z return False 2025-08-26T19:40:13.8332646Z 2025-08-26T19:40:13.8332926Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:40:13.8333516Z log.warning( 2025-08-26T19:40:13.8334303Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:40:13.8335172Z ) 2025-08-26T19:40:13.8335376Z 2025-08-26T19:40:13.8335544Z return True 2025-08-26T19:40:13.8335777Z 2025-08-26T19:40:13.8335783Z 2025-08-26T19:40:13.8335967Z def get_runner_prefix( 2025-08-26T19:40:13.8336403Z rollout_state: str, 2025-08-26T19:40:13.8336864Z workflow_requestors: Iterable[str], 2025-08-26T19:40:13.8337371Z branch: str, 2025-08-26T19:40:13.8337859Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:40:13.8338508Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:40:13.8339093Z is_canary: bool = False, 2025-08-26T19:40:13.8339538Z ) -> str: 2025-08-26T19:40:13.8340287Z settings = parse_settings(rollout_state) 2025-08-26T19:40:13.8340877Z user_optins = parse_users(rollout_state) 2025-08-26T19:40:13.8341240Z 2025-08-26T19:40:13.8341414Z fleet_prefix = "" 2025-08-26T19:40:13.8341835Z prefixes = [] 2025-08-26T19:40:13.8342453Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:40:13.8343402Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:40:13.8344107Z log.info( 2025-08-26T19:40:13.8344777Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:40:13.8345524Z ) 2025-08-26T19:40:13.8345893Z continue 2025-08-26T19:40:13.8346140Z 2025-08-26T19:40:13.8346335Z if opt_out_experiments: 2025-08-26T19:40:13.8346865Z if experiment_name in opt_out_experiments: 2025-08-26T19:40:13.8347506Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:40:13.8348087Z log.info( 2025-08-26T19:40:13.8349004Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:40:13.8350210Z ) 2025-08-26T19:40:13.8350611Z continue 2025-08-26T19:40:13.8350881Z 2025-08-26T19:40:13.8351077Z if eligible_experiments: 2025-08-26T19:40:13.8351655Z if experiment_name not in eligible_experiments: 2025-08-26T19:40:13.8352284Z exp_list = ", ".join(eligible_experiments) 2025-08-26T19:40:13.8352835Z log.info( 2025-08-26T19:40:13.8353613Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:40:13.8354441Z ) 2025-08-26T19:40:13.8354827Z continue 2025-08-26T19:40:13.8355300Z elif not experiment_settings.default: 2025-08-26T19:40:13.8355826Z log.info( 2025-08-26T19:40:13.8356620Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:40:13.8357355Z ) 2025-08-26T19:40:13.8357732Z continue 2025-08-26T19:40:13.8357974Z 2025-08-26T19:40:13.8358249Z # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:40:13.8358867Z opted_out_users = [ 2025-08-26T19:40:13.8359321Z requestor 2025-08-26T19:40:13.8359943Z for requestor in workflow_requestors 2025-08-26T19:40:13.8360628Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:40:13.8361239Z ] 2025-08-26T19:40:13.8361445Z 2025-08-26T19:40:13.8361629Z if opted_out_users: 2025-08-26T19:40:13.8362072Z log.info( 2025-08-26T19:40:13.8362679Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:40:13.8363368Z ) 2025-08-26T19:40:13.8363742Z continue 2025-08-26T19:40:13.8363999Z 2025-08-26T19:40:13.8364286Z # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:40:13.8364902Z opted_in_users = [ 2025-08-26T19:40:13.8365354Z requestor 2025-08-26T19:40:13.8365806Z for requestor in workflow_requestors 2025-08-26T19:40:13.8366474Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:40:13.8367083Z ] 2025-08-26T19:40:13.8367292Z 2025-08-26T19:40:13.8367460Z enabled = False 2025-08-26T19:40:13.8367888Z if opted_in_users: 2025-08-26T19:40:13.8368336Z log.info( 2025-08-26T19:40:13.8368937Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:40:13.8369610Z ) 2025-08-26T19:40:13.8370203Z enabled = True 2025-08-26T19:40:13.8370484Z 2025-08-26T19:40:13.8370700Z elif experiment_settings.rollout_perc: 2025-08-26T19:40:13.8371543Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:40:13.8372614Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:40:13.8373265Z log.info( 2025-08-26T19:40:13.8374130Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:40:13.8375036Z ) 2025-08-26T19:40:13.8375437Z enabled = True 2025-08-26T19:40:13.8375733Z 2025-08-26T19:40:13.8375892Z if enabled: 2025-08-26T19:40:13.8376317Z label = experiment_name 2025-08-26T19:40:13.8376861Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:40:13.8377689Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:40:13.8378557Z # - If it's enabled, then we always list it's prefix first 2025-08-26T19:40:13.8379314Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:40:13.8380163Z if is_canary: 2025-08-26T19:40:13.8380648Z label += CANARY_FLEET_SUFFIX 2025-08-26T19:40:13.8381196Z fleet_prefix = label 2025-08-26T19:40:13.8381674Z else: 2025-08-26T19:40:13.8382098Z prefixes.append(label) 2025-08-26T19:40:13.8382446Z 2025-08-26T19:40:13.8382621Z if len(prefixes) > 1: 2025-08-26T19:40:13.8383057Z log.error( 2025-08-26T19:40:13.8384085Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-26T19:40:13.8385194Z ) 2025-08-26T19:40:13.8385580Z prefixes = prefixes[:1] 2025-08-26T19:40:13.8385887Z 2025-08-26T19:40:13.8386072Z # Fleet always comes first 2025-08-26T19:40:13.8386538Z if fleet_prefix: 2025-08-26T19:40:13.8386977Z prefixes.insert(0, fleet_prefix) 2025-08-26T19:40:13.8387347Z 2025-08-26T19:40:13.8387716Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:40:13.8388135Z 2025-08-26T19:40:13.8388141Z 2025-08-26T19:40:13.8388588Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:40:13.8389348Z """ 2025-08-26T19:40:13.8390183Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:40:13.8390761Z 2025-08-26T19:40:13.8391146Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:40:13.8391848Z """ 2025-08-26T19:40:13.8392232Z gh = get_gh_client(github_token) 2025-08-26T19:40:13.8392772Z issue = get_issue(gh, repo, issue_num) 2025-08-26T19:40:13.8393401Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:40:13.8393843Z 2025-08-26T19:40:13.8393850Z 2025-08-26T19:40:13.8394253Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:40:13.8395027Z for _ in range(num_retries): 2025-08-26T19:40:13.8395499Z try: 2025-08-26T19:40:13.8395925Z req = Request(url=url, headers=headers) 2025-08-26T19:40:13.8396580Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:40:13.8397219Z return json.loads(content) 2025-08-26T19:40:13.8397737Z except Exception as e: 2025-08-26T19:40:13.8398262Z log.warning(f"Could not download {url}: {e}") 2025-08-26T19:40:13.8398663Z 2025-08-26T19:40:13.8399044Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:40:13.8400339Z return {} 2025-08-26T19:40:13.8400616Z 2025-08-26T19:40:13.8400623Z 2025-08-26T19:40:13.8400781Z @cache 2025-08-26T19:40:13.8401404Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:40:13.8402154Z """ 2025-08-26T19:40:13.8402547Z Dynamically get PR information 2025-08-26T19:40:13.8403028Z """ 2025-08-26T19:40:13.8403687Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:40:13.8404298Z headers = { 2025-08-26T19:40:13.8404747Z "Accept": "application/vnd.github.v3+json", 2025-08-26T19:40:13.8405345Z "Authorization": f"token {github_token}", 2025-08-26T19:40:13.8405876Z } 2025-08-26T19:40:13.8406288Z json_response: dict[str, Any] = download_json( 2025-08-26T19:40:13.8406893Z url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:40:13.8407429Z headers=headers, 2025-08-26T19:40:13.8407849Z ) 2025-08-26T19:40:13.8408047Z 2025-08-26T19:40:13.8408233Z if not json_response: 2025-08-26T19:40:13.8408797Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:40:13.8409408Z return {} 2025-08-26T19:40:13.8409637Z 2025-08-26T19:40:13.8410031Z return json_response 2025-08-26T19:40:13.8410329Z 2025-08-26T19:40:13.8410336Z 2025-08-26T19:40:13.8410733Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:40:13.8411458Z """ 2025-08-26T19:40:13.8411978Z Dynamically get the latest list of labels from the pull request 2025-08-26T19:40:13.8412628Z """ 2025-08-26T19:40:13.8413102Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:40:13.8413706Z return { 2025-08-26T19:40:13.8414286Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:40:13.8414981Z } 2025-08-26T19:40:13.8415178Z 2025-08-26T19:40:13.8415185Z 2025-08-26T19:40:13.8415355Z def main() -> None: 2025-08-26T19:40:13.8415772Z args = parse_args() 2025-08-26T19:40:13.8416038Z 2025-08-26T19:40:13.8416262Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:40:13.8416646Z 2025-08-26T19:40:13.8416836Z # Check if the PR is opt-out 2025-08-26T19:40:13.8417319Z if args.pr_number: 2025-08-26T19:40:13.8417958Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:40:13.8418844Z if OPT_OUT_LABEL in labels: 2025-08-26T19:40:13.8419338Z log.info( 2025-08-26T19:40:13.8420141Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:40:13.8420895Z ) 2025-08-26T19:40:13.8421436Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:40:13.8422101Z sys.exit() 2025-08-26T19:40:13.8422358Z 2025-08-26T19:40:13.8422517Z try: 2025-08-26T19:40:13.8422945Z rollout_state = get_rollout_state_from_issue( 2025-08-26T19:40:13.8423635Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:40:13.8424261Z ) 2025-08-26T19:40:13.8424464Z 2025-08-26T19:40:13.8424665Z username = get_potential_pr_author( 2025-08-26T19:40:13.8425203Z args.github_token, 2025-08-26T19:40:13.8425672Z args.github_repo, 2025-08-26T19:40:13.8426141Z args.github_actor, 2025-08-26T19:40:13.8426624Z args.github_ref_type, 2025-08-26T19:40:13.8427109Z args.github_branch, 2025-08-26T19:40:13.8427606Z ) 2025-08-26T19:40:13.8427809Z 2025-08-26T19:40:13.8428087Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:40:13.8428539Z 2025-08-26T19:40:13.8428751Z runner_label_prefix = get_runner_prefix( 2025-08-26T19:40:13.8429293Z rollout_state, 2025-08-26T19:40:13.8429863Z (args.github_issue_owner, username), 2025-08-26T19:40:13.8430408Z args.github_branch, 2025-08-26T19:40:13.8430900Z args.eligible_experiments, 2025-08-26T19:40:13.8431426Z args.opt_out_experiments, 2025-08-26T19:40:13.8431918Z is_canary, 2025-08-26T19:40:13.8432321Z ) 2025-08-26T19:40:13.8432526Z 2025-08-26T19:40:13.8432705Z except Exception as e: 2025-08-26T19:40:13.8433153Z log.error( 2025-08-26T19:40:13.8433805Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:40:13.8434696Z ) 2025-08-26T19:40:13.8434900Z 2025-08-26T19:40:13.8435228Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:40:13.8435721Z 2025-08-26T19:40:13.8435728Z 2025-08-26T19:40:13.8435903Z if __name__ == "__main__": 2025-08-26T19:40:13.8436328Z main() 2025-08-26T19:40:13.8436535Z 2025-08-26T19:40:13.8528635Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:40:13.8529908Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:40:13.8570555Z shell: /usr/bin/bash -e {0} 2025-08-26T19:40:13.8571296Z env: 2025-08-26T19:40:13.8572030Z GITHUB_TOKEN: *** 2025-08-26T19:40:13.8572520Z ISSUE_NUMBER: 5132 2025-08-26T19:40:13.8573152Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:40:13.8573795Z ISSUE_OWNER: 2025-08-26T19:40:13.8574297Z CHECK_EXPERIMENTS: 2025-08-26T19:40:13.8574910Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:40:13.8575473Z PR_NUMBER: 2025-08-26T19:40:13.8575946Z ##[endgroup] 2025-08-26T19:40:14.1883278Z Defaulting to user installation because normal site-packages is not writeable 2025-08-26T19:40:14.6606620Z Collecting urllib3==1.26.18 2025-08-26T19:40:14.6984373Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-26T19:40:14.7227713Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.9 MB/s eta 0:00:00 2025-08-26T19:40:14.7489118Z Collecting PyGithub==2.3.0 2025-08-26T19:40:14.7521390Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-26T19:40:14.7975496Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-26T19:40:14.8009943Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl.metadata (8.6 kB) 2025-08-26T19:40:14.8052674Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-26T19:40:14.8071352Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-08-26T19:40:14.8087853Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-26T19:40:14.8368017Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-26T19:40:14.8400370Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-26T19:40:14.8627758Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-08-26T19:40:14.9802136Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:40:14.9835807Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-26T19:40:15.1069546Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-26T19:40:15.1130641Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-08-26T19:40:15.1410028Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:40:15.1441757Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-26T19:40:15.1667525Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-26T19:40:15.1725785Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 34.0 MB/s eta 0:00:00 2025-08-26T19:40:15.1813271Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-26T19:40:15.1879412Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 73.8 MB/s eta 0:00:00 2025-08-26T19:40:15.1918038Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-08-26T19:40:15.2012741Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 115.1 MB/s eta 0:00:00 2025-08-26T19:40:15.2043105Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-26T19:40:15.2097317Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-26T19:40:15.2162254Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 103.8 MB/s eta 0:00:00 2025-08-26T19:40:15.2198503Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-08-26T19:40:15.2237591Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 34.4 MB/s eta 0:00:00 2025-08-26T19:40:15.2270001Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-26T19:40:15.2310716Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 44.1 MB/s eta 0:00:00 2025-08-26T19:40:15.5117581Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-26T19:40:16.0467356Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-08-26T19:40:16.1248079Z ##[group]Run curr_branch="main" 2025-08-26T19:40:16.1248479Z curr_branch="main" 2025-08-26T19:40:16.1248890Z curr_ref_type="branch" 2025-08-26T19:40:16.1249318Z echo "Current branch is '$curr_branch'" 2025-08-26T19:40:16.1249661Z  2025-08-26T19:40:16.1250218Z python3 runner_determinator.py \ 2025-08-26T19:40:16.1250599Z  --github-token "$GITHUB_TOKEN" \ 2025-08-26T19:40:16.1251023Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-26T19:40:16.1251349Z  --github-branch "$curr_branch" \ 2025-08-26T19:40:16.1251804Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-26T19:40:16.1252209Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-26T19:40:16.1252559Z  --github-ref-type "$curr_ref_type" \ 2025-08-26T19:40:16.1253072Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-26T19:40:16.1253466Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-26T19:40:16.1254069Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-26T19:40:16.1254454Z  --pr-number "${PR_NUMBER}" 2025-08-26T19:40:16.1297526Z shell: /usr/bin/bash -e {0} 2025-08-26T19:40:16.1297900Z env: 2025-08-26T19:40:16.1298584Z GITHUB_TOKEN: *** 2025-08-26T19:40:16.1298843Z ISSUE_NUMBER: 5132 2025-08-26T19:40:16.1299104Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:40:16.1299395Z ISSUE_OWNER: 2025-08-26T19:40:16.1299630Z CHECK_EXPERIMENTS: 2025-08-26T19:40:16.1300109Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:40:16.1300342Z PR_NUMBER: 2025-08-26T19:40:16.1300559Z ##[endgroup] 2025-08-26T19:40:16.1361655Z Current branch is 'main' 2025-08-26T19:40:17.4944606Z INFO : Based on rollout percentage of 75%, enabling experiment lf. 2025-08-26T19:40:17.4945518Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-26T19:40:17.4946594Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-26T19:40:17.4947291Z INFO : Setting output: label-type='lf.' 2025-08-26T19:40:17.5258655Z Evaluate and set job outputs 2025-08-26T19:40:17.5264920Z Set output 'label-type' 2025-08-26T19:40:17.5266697Z Cleaning up orphan processes