2025-08-14T20:43:47.0395705Z Current runner version: '2.327.1' 2025-08-14T20:43:47.0418622Z ##[group]Runner Image Provisioner 2025-08-14T20:43:47.0419364Z Hosted Compute Agent 2025-08-14T20:43:47.0420003Z Version: 20250812.370 2025-08-14T20:43:47.0420563Z Commit: 4a2b2bf7520004e3e907c2150c8cabe342a3da32 2025-08-14T20:43:47.0421229Z Build Date: 2025-08-12T16:08:14Z 2025-08-14T20:43:47.0422101Z ##[endgroup] 2025-08-14T20:43:47.0422652Z ##[group]Operating System 2025-08-14T20:43:47.0423167Z Ubuntu 2025-08-14T20:43:47.0423689Z 24.04.2 2025-08-14T20:43:47.0424108Z LTS 2025-08-14T20:43:47.0424553Z ##[endgroup] 2025-08-14T20:43:47.0425027Z ##[group]Runner Image 2025-08-14T20:43:47.0425607Z Image: ubuntu-24.04 2025-08-14T20:43:47.0426237Z Version: 20250804.2.0 2025-08-14T20:43:47.0427859Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250804.2/images/ubuntu/Ubuntu2404-Readme.md 2025-08-14T20:43:47.0430394Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250804.2 2025-08-14T20:43:47.0432208Z ##[endgroup] 2025-08-14T20:43:47.0433642Z ##[group]GITHUB_TOKEN Permissions 2025-08-14T20:43:47.0436495Z Metadata: read 2025-08-14T20:43:47.0437262Z ##[endgroup] 2025-08-14T20:43:47.0440049Z Secret source: Actions 2025-08-14T20:43:47.0441873Z Prepare workflow directory 2025-08-14T20:43:47.1157038Z Prepare all required actions 2025-08-14T20:43:47.1237105Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (1fc683cf17c8c673044538d10266c00f92987be2) 2025-08-14T20:43:47.1244969Z ##[group] Inputs 2025-08-14T20:43:47.1245960Z check_experiments: 2025-08-14T20:43:47.1246818Z opt_out_experiments: 2025-08-14T20:43:47.1247818Z triggering_actor: pytorchmergebot 2025-08-14T20:43:47.1248723Z issue_owner: 2025-08-14T20:43:47.1249601Z curr_branch: main 2025-08-14T20:43:47.1250531Z curr_ref_type: branch 2025-08-14T20:43:47.1251398Z issue_number: 5132 2025-08-14T20:43:47.1252576Z ##[endgroup] 2025-08-14T20:43:47.1253614Z Complete job name: get-label-type / runner-determinator 2025-08-14T20:43:47.6680484Z ##[group]Run cat < runner_determinator.py 2025-08-14T20:43:47.6683145Z cat < runner_determinator.py 2025-08-14T20:43:47.6683936Z # flake8: noqa: G004 2025-08-14T20:43:47.6684484Z  2025-08-14T20:43:47.6685283Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:43:47.6686494Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:43:47.6687471Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:43:47.6688210Z  2025-08-14T20:43:47.6688678Z """ 2025-08-14T20:43:47.6689447Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:43:47.6690489Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:43:47.6691861Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:43:47.6692860Z of which runners should be used to run which job. 2025-08-14T20:43:47.6693549Z  2025-08-14T20:43:47.6694374Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:43:47.6695431Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:43:47.6696458Z settings are considered to be empty with only the second part, the user 2025-08-14T20:43:47.6697402Z list, defined. 2025-08-14T20:43:47.6697904Z  2025-08-14T20:43:47.6698593Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:43:47.6699715Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:43:47.6700737Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:43:47.6701692Z  2025-08-14T20:43:47.6702555Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:43:47.6703935Z The user list is also a comma separated list of additional features or 2025-08-14T20:43:47.6704829Z experiments which the user could be opted in to. 2025-08-14T20:43:47.6705596Z  2025-08-14T20:43:47.6706088Z The user list has the following rules: 2025-08-14T20:43:47.6706738Z  2025-08-14T20:43:47.6707500Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:43:47.6708514Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:43:47.6709457Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:43:47.6710133Z  2025-08-14T20:43:47.6710636Z Example config: 2025-08-14T20:43:47.6711233Z  # A list of experiments that can be opted into. 2025-08-14T20:43:47.6712349Z  # This defines the behavior they'll induce when opted into. 2025-08-14T20:43:47.6713195Z  # Expected syntax is: 2025-08-14T20:43:47.6713993Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:43:47.6796348Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:43:47.6798043Z  2025-08-14T20:43:47.6798783Z  experiments: 2025-08-14T20:43:47.6799635Z  lf: 2025-08-14T20:43:47.6800495Z  rollout_percent: 25 2025-08-14T20:43:47.6801391Z  all_branches: false 2025-08-14T20:43:47.6802321Z  default: true 2025-08-14T20:43:47.6802815Z  --- 2025-08-14T20:43:47.6803229Z  2025-08-14T20:43:47.6803613Z  # Opt-ins: 2025-08-14T20:43:47.6804302Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:43:47.6805502Z  # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:43:47.6806384Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:43:47.6807128Z  # Experiments should be from the above list. 2025-08-14T20:43:47.6807724Z  2025-08-14T20:43:47.6808140Z  @User1,-lf,split_build 2025-08-14T20:43:47.6808652Z  @User2,lf 2025-08-14T20:43:47.6809117Z  @User3,split_build 2025-08-14T20:43:47.6809606Z """ 2025-08-14T20:43:47.6809992Z  2025-08-14T20:43:47.6810376Z import json 2025-08-14T20:43:47.6810817Z import logging 2025-08-14T20:43:47.6811259Z import os 2025-08-14T20:43:47.6811917Z import random 2025-08-14T20:43:47.6812363Z import re 2025-08-14T20:43:47.6812789Z import sys 2025-08-14T20:43:47.6813267Z from argparse import ArgumentParser 2025-08-14T20:43:47.6813946Z from collections.abc import Iterable 2025-08-14T20:43:47.6814553Z from functools import cache 2025-08-14T20:43:47.6815101Z from logging import LogRecord 2025-08-14T20:43:47.6815671Z from typing import Any, NamedTuple 2025-08-14T20:43:47.6816307Z from urllib.request import Request, urlopen 2025-08-14T20:43:47.6816901Z  2025-08-14T20:43:47.6817295Z import yaml 2025-08-14T20:43:47.6817761Z from github import Auth, Github 2025-08-14T20:43:47.6818329Z from github.Issue import Issue 2025-08-14T20:43:47.6818852Z  2025-08-14T20:43:47.6819221Z  2025-08-14T20:43:47.6819691Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:43:47.6820462Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:43:47.6821429Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:43:47.6822374Z  2025-08-14T20:43:47.6822868Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:43:47.6823685Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:43:47.6824296Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:43:47.6824954Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:43:47.6825523Z  2025-08-14T20:43:47.6825962Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:43:47.6826507Z  2025-08-14T20:43:47.6826918Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:43:47.6827451Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:43:47.6827953Z  2025-08-14T20:43:47.6828325Z  2025-08-14T20:43:47.6828744Z class Experiment(NamedTuple): 2025-08-14T20:43:47.6829305Z  rollout_perc: float = ( 2025-08-14T20:43:47.6830052Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:43:47.6830784Z  ) 2025-08-14T20:43:47.6831207Z  all_branches: bool = ( 2025-08-14T20:43:47.6832191Z  False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:43:47.6832919Z  ) 2025-08-14T20:43:47.6833335Z  default: bool = ( 2025-08-14T20:43:47.6834014Z  True # If True, the experiment is enabled by default for all queries 2025-08-14T20:43:47.6834699Z  ) 2025-08-14T20:43:47.6835095Z  2025-08-14T20:43:47.6835502Z  # Add more fields as needed 2025-08-14T20:43:47.6836018Z  2025-08-14T20:43:47.6836381Z  2025-08-14T20:43:47.6836789Z class Settings(NamedTuple): 2025-08-14T20:43:47.6837292Z  """ 2025-08-14T20:43:47.6837831Z  Settings for the experiments that can be opted into. 2025-08-14T20:43:47.6838456Z  """ 2025-08-14T20:43:47.6838855Z  2025-08-14T20:43:47.6839304Z  experiments: dict[str, Experiment] = {} 2025-08-14T20:43:47.6839854Z  2025-08-14T20:43:47.6840372Z  2025-08-14T20:43:47.6840839Z class ColorFormatter(logging.Formatter): 2025-08-14T20:43:47.6841743Z  """Color codes the log messages based on the log level""" 2025-08-14T20:43:47.6842384Z  2025-08-14T20:43:47.6842771Z  COLORS = { 2025-08-14T20:43:47.6843244Z  "WARNING": "\033[33m", # Yellow 2025-08-14T20:43:47.6843817Z  "ERROR": "\033[31m", # Red 2025-08-14T20:43:47.6844379Z  "CRITICAL": "\033[31m", # Red 2025-08-14T20:43:47.6844935Z  "INFO": "\033[0m", # Reset 2025-08-14T20:43:47.6845492Z  "DEBUG": "\033[0m", # Reset 2025-08-14T20:43:47.6846011Z  } 2025-08-14T20:43:47.6846407Z  2025-08-14T20:43:47.6846872Z  def format(self, record: LogRecord) -> str: 2025-08-14T20:43:47.6847710Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:43:47.6848553Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:43:47.6849195Z  return super().format(record) 2025-08-14T20:43:47.6849734Z  2025-08-14T20:43:47.6850102Z  2025-08-14T20:43:47.6850526Z handler = logging.StreamHandler() 2025-08-14T20:43:47.6851325Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:43:47.6852309Z  2025-08-14T20:43:47.6852833Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:43:47.6853486Z log.addHandler(handler) 2025-08-14T20:43:47.6854011Z log.setLevel(logging.INFO) 2025-08-14T20:43:47.6854509Z  2025-08-14T20:43:47.6854880Z  2025-08-14T20:43:47.6855379Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:43:47.6856020Z  """ 2025-08-14T20:43:47.6856593Z  Defines outputs of the github action that invokes this script 2025-08-14T20:43:47.6857276Z  """ 2025-08-14T20:43:47.6857836Z  if not GITHUB_OUTPUT: 2025-08-14T20:43:47.6858996Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:43:47.6860191Z  log.warning( 2025-08-14T20:43:47.6861138Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:43:47.6862327Z  ) 2025-08-14T20:43:47.6862834Z  print(f"::set-output name={key}::{value}") 2025-08-14T20:43:47.6863429Z  return 2025-08-14T20:43:47.6863871Z  2025-08-14T20:43:47.6864301Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:43:47.6864948Z  log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:43:47.6865580Z  f.write(f"{key}={value}\n") 2025-08-14T20:43:47.6866121Z  2025-08-14T20:43:47.6866510Z  2025-08-14T20:43:47.6867080Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:43:47.6867795Z  return frozenset( 2025-08-14T20:43:47.6868511Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:43:47.6869241Z  ) 2025-08-14T20:43:47.6869644Z  2025-08-14T20:43:47.6870023Z  2025-08-14T20:43:47.6870423Z def parse_args() -> Any: 2025-08-14T20:43:47.6871086Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:43:47.6872265Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:43:47.6873091Z  parser.add_argument( 2025-08-14T20:43:47.6873635Z  "--github-issue-repo", 2025-08-14T20:43:47.6874171Z  type=str, 2025-08-14T20:43:47.6874657Z  required=False, 2025-08-14T20:43:47.6875336Z  default="pytorch/test-infra", 2025-08-14T20:43:47.6875961Z  help="GitHub repo to get the issue", 2025-08-14T20:43:47.6876519Z  ) 2025-08-14T20:43:47.6876947Z  parser.add_argument( 2025-08-14T20:43:47.6877463Z  "--github-repo", 2025-08-14T20:43:47.6877970Z  type=str, 2025-08-14T20:43:47.6878449Z  required=True, 2025-08-14T20:43:47.6879004Z  help="GitHub repo where CI is running", 2025-08-14T20:43:47.6879574Z  ) 2025-08-14T20:43:47.6879998Z  parser.add_argument( 2025-08-14T20:43:47.6880704Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:43:47.6881411Z  ) 2025-08-14T20:43:47.6882165Z  parser.add_argument( 2025-08-14T20:43:47.6882891Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:43:47.6883617Z  ) 2025-08-14T20:43:47.6884051Z  parser.add_argument( 2025-08-14T20:43:47.6884785Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:43:47.6885562Z  ) 2025-08-14T20:43:47.6885990Z  parser.add_argument( 2025-08-14T20:43:47.6886754Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:43:47.6887514Z  ) 2025-08-14T20:43:47.6887956Z  parser.add_argument( 2025-08-14T20:43:47.6888484Z  "--github-ref-type", 2025-08-14T20:43:47.6889020Z  type=str, 2025-08-14T20:43:47.6889498Z  required=True, 2025-08-14T20:43:47.6890093Z  help="Current GitHub ref type, branch or tag", 2025-08-14T20:43:47.6890703Z  ) 2025-08-14T20:43:47.6891129Z  parser.add_argument( 2025-08-14T20:43:47.6891869Z  "--eligible-experiments", 2025-08-14T20:43:47.6892620Z  type=_str_comma_separated_to_set, 2025-08-14T20:43:47.6893203Z  required=False, 2025-08-14T20:43:47.6893697Z  default="", 2025-08-14T20:43:47.6894653Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:43:47.6895626Z  ) 2025-08-14T20:43:47.6896050Z  parser.add_argument( 2025-08-14T20:43:47.6896590Z  "--opt-out-experiments", 2025-08-14T20:43:47.6897170Z  type=_str_comma_separated_to_set, 2025-08-14T20:43:47.6897745Z  required=False, 2025-08-14T20:43:47.6898240Z  default="", 2025-08-14T20:43:47.6898716Z  help=( 2025-08-14T20:43:47.6899462Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:43:47.6900668Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:43:47.6901747Z  ), 2025-08-14T20:43:47.6902178Z  ) 2025-08-14T20:43:47.6902614Z  parser.add_argument( 2025-08-14T20:43:47.6903128Z  "--pr-number", 2025-08-14T20:43:47.6903628Z  type=str, 2025-08-14T20:43:47.6904103Z  required=False, 2025-08-14T20:43:47.6904599Z  default="", 2025-08-14T20:43:47.6905173Z  help="the optional PR number where this is run", 2025-08-14T20:43:47.6905779Z  ) 2025-08-14T20:43:47.6906181Z  2025-08-14T20:43:47.6906597Z  return parser.parse_args() 2025-08-14T20:43:47.6907124Z  2025-08-14T20:43:47.6907496Z  2025-08-14T20:43:47.6908152Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.6909118Z  auth = Auth.Token(github_token) 2025-08-14T20:43:47.6909723Z  return Github(auth=auth) 2025-08-14T20:43:47.6910241Z  2025-08-14T20:43:47.6910616Z  2025-08-14T20:43:47.6911341Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.6912428Z  repo = gh.get_repo(repo) 2025-08-14T20:43:47.6913025Z  return repo.get_issue(number=issue_num) 2025-08-14T20:43:47.6913604Z  2025-08-14T20:43:47.6913988Z  2025-08-14T20:43:47.6914401Z def get_potential_pr_author( 2025-08-14T20:43:47.6915146Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:43:47.6915888Z ) -> str: 2025-08-14T20:43:47.6916485Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:43:47.6917378Z  # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:43:47.6918199Z  # embedded in the tag name: ciflow// 2025-08-14T20:43:47.6918828Z  2025-08-14T20:43:47.6919261Z  gh = get_gh_client(github_token) 2025-08-14T20:43:47.6919800Z  2025-08-14T20:43:47.6920323Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:43:47.6921020Z  split_tag = ref_name.split("/") 2025-08-14T20:43:47.6921770Z  if ( 2025-08-14T20:43:47.6922236Z  len(split_tag) == 3 2025-08-14T20:43:47.6922811Z  and split_tag[0] == "ciflow" 2025-08-14T20:43:47.6923409Z  and split_tag[2].isnumeric() 2025-08-14T20:43:47.6923946Z  ): 2025-08-14T20:43:47.6924422Z  pr_number = split_tag[2] 2025-08-14T20:43:47.6924972Z  try: 2025-08-14T20:43:47.6925490Z  repository = gh.get_repo(repo) 2025-08-14T20:43:47.6926188Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:43:47.6927013Z  except Exception as e: 2025-08-14T20:43:47.6927619Z  raise Exception( # noqa: TRY002 2025-08-14T20:43:47.6928359Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:43:47.6929064Z  ) from e 2025-08-14T20:43:47.6929692Z  return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:43:47.6930472Z  # In all other cases, return the original input username 2025-08-14T20:43:47.6931125Z  return username 2025-08-14T20:43:47.6931790Z  2025-08-14T20:43:47.6932199Z  2025-08-14T20:43:47.6932673Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:43:47.6933258Z  """ 2025-08-14T20:43:47.6933988Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:43:47.6934840Z  """ 2025-08-14T20:43:47.6935448Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:43:47.6936167Z  2025-08-14T20:43:47.6936538Z  2025-08-14T20:43:47.6936966Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:43:47.6937530Z  try: 2025-08-14T20:43:47.6937986Z  data = yaml.safe_load(yaml_text) 2025-08-14T20:43:47.6938556Z  return data 2025-08-14T20:43:47.6939051Z  except yaml.YAMLError: 2025-08-14T20:43:47.6939627Z  log.exception("Error loading YAML") 2025-08-14T20:43:47.6940192Z  raise 2025-08-14T20:43:47.6940620Z  2025-08-14T20:43:47.6940985Z  2025-08-14T20:43:47.6941844Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:43:47.6942651Z  """ 2025-08-14T20:43:47.6943475Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:43:47.6944280Z  2025-08-14T20:43:47.6944866Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.6945710Z  and the text below is the list of opted in users. 2025-08-14T20:43:47.6946315Z  2025-08-14T20:43:47.6946938Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:43:47.6947693Z  """ 2025-08-14T20:43:47.6948202Z  rollout_state_parts = rollout_state.split("---") 2025-08-14T20:43:47.6948870Z  if len(rollout_state_parts) >= 2: 2025-08-14T20:43:47.6949553Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:43:47.6950444Z  else: 2025-08-14T20:43:47.6950898Z  return "", rollout_state 2025-08-14T20:43:47.6951407Z  2025-08-14T20:43:47.6951999Z  2025-08-14T20:43:47.6952459Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:43:47.6953038Z  """ 2025-08-14T20:43:47.6953632Z  Dictionary of users with a list of features they have opted into 2025-08-14T20:43:47.6954335Z  """ 2025-08-14T20:43:47.6954725Z  2025-08-14T20:43:47.6955091Z  2025-08-14T20:43:47.6955672Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:43:47.6956379Z  """ 2025-08-14T20:43:47.6957168Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:43:47.6958043Z  2025-08-14T20:43:47.6958909Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:43:47.6959968Z  - Example line: "@User1,lf,split_build" 2025-08-14T20:43:47.6960720Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:43:47.6963038Z  2025-08-14T20:43:47.6963396Z  2025-08-14T20:43:47.6963764Z  """ 2025-08-14T20:43:47.6964182Z  optins = UserOptins() 2025-08-14T20:43:47.6964751Z  for user in user_optin_text.split("\n"): 2025-08-14T20:43:47.6965357Z  user = user.strip("\r\n\t -") 2025-08-14T20:43:47.6965971Z  if not user or not user.startswith("@"): 2025-08-14T20:43:47.6966573Z  # Not a valid user. Skip 2025-08-14T20:43:47.6967110Z  continue 2025-08-14T20:43:47.6967594Z  2025-08-14T20:43:47.6967970Z  if user: 2025-08-14T20:43:47.6968489Z  usr_name = user.split(",")[0].strip("@") 2025-08-14T20:43:47.6969244Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:43:47.6969922Z  2025-08-14T20:43:47.6970305Z  return optins 2025-08-14T20:43:47.6970760Z  2025-08-14T20:43:47.6971118Z  2025-08-14T20:43:47.6971771Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:43:47.6972438Z  """ 2025-08-14T20:43:47.6972886Z  Check if the experiment name is valid. 2025-08-14T20:43:47.6973459Z  A valid name: 2025-08-14T20:43:47.6974178Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:43:47.6975176Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:43:47.6975941Z  - Cannot contain spaces 2025-08-14T20:43:47.6976449Z  """ 2025-08-14T20:43:47.6976831Z  2025-08-14T20:43:47.6977324Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:43:47.6978109Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:43:47.6978866Z  2025-08-14T20:43:47.6979497Z  if valid: 2025-08-14T20:43:47.6979936Z  return True 2025-08-14T20:43:47.6980377Z  2025-08-14T20:43:47.6980748Z  log.error( 2025-08-14T20:43:47.6982381Z  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-14T20:43:47.6983972Z  ) 2025-08-14T20:43:47.6984364Z  return False 2025-08-14T20:43:47.6984812Z  2025-08-14T20:43:47.6985168Z  2025-08-14T20:43:47.6985754Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:43:47.6986433Z  """ 2025-08-14T20:43:47.6987109Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:43:47.6987870Z  """ 2025-08-14T20:43:47.6988266Z  try: 2025-08-14T20:43:47.6988677Z  if settings_text: 2025-08-14T20:43:47.6989479Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:43:47.6990314Z  # for easy reading 2025-08-14T20:43:47.6991188Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:43:47.6992234Z  # the backtick character in shell commands. 2025-08-14T20:43:47.6992894Z  backtick = chr(96) # backtick character 2025-08-14T20:43:47.6993625Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:43:47.6994341Z  settings = load_yaml(settings_text) 2025-08-14T20:43:47.6994889Z  2025-08-14T20:43:47.6995527Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:43:47.6996446Z  experiments = {} 2025-08-14T20:43:47.6996933Z  2025-08-14T20:43:47.6997530Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:43:47.6998346Z  if not is_valid_experiment_name(exp_name): 2025-08-14T20:43:47.6999514Z  # 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-14T20:43:47.7000604Z  continue 2025-08-14T20:43:47.7001092Z  2025-08-14T20:43:47.7001601Z  valid_settings = {} 2025-08-14T20:43:47.7002190Z  for setting in exp_settings: 2025-08-14T20:43:47.7002808Z  if setting not in Experiment._fields: 2025-08-14T20:43:47.7003418Z  log.warning( 2025-08-14T20:43:47.7004207Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:43:47.7004967Z  ) 2025-08-14T20:43:47.7005458Z  else: 2025-08-14T20:43:47.7006056Z  valid_settings[setting] = exp_settings[setting] 2025-08-14T20:43:47.7006661Z  2025-08-14T20:43:47.7007178Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:43:47.7007873Z  return Settings(experiments) 2025-08-14T20:43:47.7008408Z  2025-08-14T20:43:47.7008803Z  except Exception: 2025-08-14T20:43:47.7009371Z  log.exception("Failed to parse settings") 2025-08-14T20:43:47.7009951Z  2025-08-14T20:43:47.7010338Z  return Settings() 2025-08-14T20:43:47.7010804Z  2025-08-14T20:43:47.7011159Z  2025-08-14T20:43:47.7011872Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:43:47.7012523Z  """ 2025-08-14T20:43:47.7013021Z  Parse settings, if any, from the rollout state. 2025-08-14T20:43:47.7013621Z  2025-08-14T20:43:47.7014201Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.7015030Z  and the text below is the list of opted in users. 2025-08-14T20:43:47.7015619Z  2025-08-14T20:43:47.7016257Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:43:47.7017030Z  """ 2025-08-14T20:43:47.7017646Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.7018474Z  return parse_settings_from_text(settings_text) 2025-08-14T20:43:47.7019059Z  2025-08-14T20:43:47.7019417Z  2025-08-14T20:43:47.7019911Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:43:47.7020532Z  """ 2025-08-14T20:43:47.7020991Z  Parse users from the rollout state. 2025-08-14T20:43:47.7021638Z  2025-08-14T20:43:47.7022009Z  """ 2025-08-14T20:43:47.7022611Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.7023438Z  return parse_user_opt_in_from_text(users_text) 2025-08-14T20:43:47.7024024Z  2025-08-14T20:43:47.7024392Z  2025-08-14T20:43:47.7025058Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.7025863Z  """ 2025-08-14T20:43:47.7026345Z  Check if a user is opted into an experiment 2025-08-14T20:43:47.7026921Z  """ 2025-08-14T20:43:47.7027453Z  return experiment_name in user_optins.get(user, []) 2025-08-14T20:43:47.7028078Z  2025-08-14T20:43:47.7028439Z  2025-08-14T20:43:47.7029255Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.7030070Z  """ 2025-08-14T20:43:47.7030606Z  Check if a user explicitly opted out of an experiment 2025-08-14T20:43:47.7031221Z  """ 2025-08-14T20:43:47.7031896Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:43:47.7032655Z  experiment_optout = "-" + experiment_name 2025-08-14T20:43:47.7033368Z  if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:43:47.7034022Z  return False 2025-08-14T20:43:47.7034490Z  2025-08-14T20:43:47.7035003Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:43:47.7035645Z  log.warning( 2025-08-14T20:43:47.7036550Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:43:47.7037472Z  ) 2025-08-14T20:43:47.7037884Z  2025-08-14T20:43:47.7038262Z  return True 2025-08-14T20:43:47.7038714Z  2025-08-14T20:43:47.7039077Z  2025-08-14T20:43:47.7039472Z def get_runner_prefix( 2025-08-14T20:43:47.7039974Z  rollout_state: str, 2025-08-14T20:43:47.7040509Z  workflow_requestors: Iterable[str], 2025-08-14T20:43:47.7041076Z  branch: str, 2025-08-14T20:43:47.7041801Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.7042554Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.7043186Z  is_canary: bool = False, 2025-08-14T20:43:47.7043700Z ) -> str: 2025-08-14T20:43:47.7044223Z  settings = parse_settings(rollout_state) 2025-08-14T20:43:47.7044866Z  user_optins = parse_users(rollout_state) 2025-08-14T20:43:47.7045434Z  2025-08-14T20:43:47.7045947Z  fleet_prefix = "" 2025-08-14T20:43:47.7046445Z  prefixes = [] 2025-08-14T20:43:47.7047161Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:43:47.7048172Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:43:47.7048931Z  log.info( 2025-08-14T20:43:47.7049685Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:43:47.7050481Z  ) 2025-08-14T20:43:47.7050933Z  continue 2025-08-14T20:43:47.7051388Z  2025-08-14T20:43:47.7051908Z  if opt_out_experiments: 2025-08-14T20:43:47.7052525Z  if experiment_name in opt_out_experiments: 2025-08-14T20:43:47.7053238Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:43:47.7053882Z  log.info( 2025-08-14T20:43:47.7054900Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:43:47.7055917Z  ) 2025-08-14T20:43:47.7056382Z  continue 2025-08-14T20:43:47.7056886Z  2025-08-14T20:43:47.7057300Z  if eligible_experiments: 2025-08-14T20:43:47.7057933Z  if experiment_name not in eligible_experiments: 2025-08-14T20:43:47.7058625Z  exp_list = ", ".join(eligible_experiments) 2025-08-14T20:43:47.7059224Z  log.info( 2025-08-14T20:43:47.7060078Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:43:47.7060958Z  ) 2025-08-14T20:43:47.7061412Z  continue 2025-08-14T20:43:47.7062229Z  elif not experiment_settings.default: 2025-08-14T20:43:47.7062813Z  log.info( 2025-08-14T20:43:47.7063553Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:43:47.7064328Z  ) 2025-08-14T20:43:47.7064760Z  continue 2025-08-14T20:43:47.7065218Z  2025-08-14T20:43:47.7065727Z  # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:43:47.7066395Z  opted_out_users = [ 2025-08-14T20:43:47.7066918Z  requestor 2025-08-14T20:43:47.7067453Z  for requestor in workflow_requestors 2025-08-14T20:43:47.7068188Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.7068854Z  ] 2025-08-14T20:43:47.7069261Z  2025-08-14T20:43:47.7069648Z  if opted_out_users: 2025-08-14T20:43:47.7070186Z  log.info( 2025-08-14T20:43:47.7070893Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:43:47.7071739Z  ) 2025-08-14T20:43:47.7072184Z  continue 2025-08-14T20:43:47.7072634Z  2025-08-14T20:43:47.7073147Z  # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:43:47.7073806Z  opted_in_users = [ 2025-08-14T20:43:47.7074338Z  requestor 2025-08-14T20:43:47.7074889Z  for requestor in workflow_requestors 2025-08-14T20:43:47.7075628Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.7076293Z  ] 2025-08-14T20:43:47.7076696Z  2025-08-14T20:43:47.7077095Z  enabled = False 2025-08-14T20:43:47.7077601Z  if opted_in_users: 2025-08-14T20:43:47.7078233Z  log.info( 2025-08-14T20:43:47.7078925Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:43:47.7079650Z  ) 2025-08-14T20:43:47.7080098Z  enabled = True 2025-08-14T20:43:47.7080585Z  2025-08-14T20:43:47.7081036Z  elif experiment_settings.rollout_perc: 2025-08-14T20:43:47.7082039Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:43:47.7083053Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:43:47.7083747Z  log.info( 2025-08-14T20:43:47.7084697Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:43:47.7085684Z  ) 2025-08-14T20:43:47.7086167Z  enabled = True 2025-08-14T20:43:47.7086679Z  2025-08-14T20:43:47.7087064Z  if enabled: 2025-08-14T20:43:47.7087566Z  label = experiment_name 2025-08-14T20:43:47.7088178Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:43:47.7089066Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:43:47.7089995Z  # - If it's enabled, then we always list it's prefix first 2025-08-14T20:43:47.7090816Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:43:47.7091622Z  if is_canary: 2025-08-14T20:43:47.7092189Z  label += CANARY_FLEET_SUFFIX 2025-08-14T20:43:47.7092790Z  fleet_prefix = label 2025-08-14T20:43:47.7093341Z  else: 2025-08-14T20:43:47.7093848Z  prefixes.append(label) 2025-08-14T20:43:47.7094527Z  2025-08-14T20:43:47.7094924Z  if len(prefixes) > 1: 2025-08-14T20:43:47.7095432Z  log.error( 2025-08-14T20:43:47.7096553Z  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-14T20:43:47.7097724Z  ) 2025-08-14T20:43:47.7098180Z  prefixes = prefixes[:1] 2025-08-14T20:43:47.7098692Z  2025-08-14T20:43:47.7099101Z  # Fleet always comes first 2025-08-14T20:43:47.7099638Z  if fleet_prefix: 2025-08-14T20:43:47.7100163Z  prefixes.insert(0, fleet_prefix) 2025-08-14T20:43:47.7100709Z  2025-08-14T20:43:47.7101204Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:43:47.7101930Z  2025-08-14T20:43:47.7102300Z  2025-08-14T20:43:47.7103003Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:43:47.7103810Z  """ 2025-08-14T20:43:47.7104462Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:43:47.7105201Z  2025-08-14T20:43:47.7105824Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:43:47.7106577Z  """ 2025-08-14T20:43:47.7107019Z  gh = get_gh_client(github_token) 2025-08-14T20:43:47.7107634Z  issue = get_issue(gh, repo, issue_num) 2025-08-14T20:43:47.7108339Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:43:47.7108984Z  2025-08-14T20:43:47.7109344Z  2025-08-14T20:43:47.7109984Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:43:47.7110894Z  for _ in range(num_retries): 2025-08-14T20:43:47.7111433Z  try: 2025-08-14T20:43:47.7112037Z  req = Request(url=url, headers=headers) 2025-08-14T20:43:47.7112754Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:43:47.7113447Z  return json.loads(content) 2025-08-14T20:43:47.7114016Z  except Exception as e: 2025-08-14T20:43:47.7114631Z  log.warning(f"Could not download {url}: {e}") 2025-08-14T20:43:47.7115218Z  2025-08-14T20:43:47.7115851Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:43:47.7116615Z  return {} 2025-08-14T20:43:47.7117041Z  2025-08-14T20:43:47.7117409Z  2025-08-14T20:43:47.7117773Z @cache 2025-08-14T20:43:47.7118467Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:43:47.7119265Z  """ 2025-08-14T20:43:47.7119794Z  Dynamically get PR information 2025-08-14T20:43:47.7120340Z  """ 2025-08-14T20:43:47.7120894Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:43:47.7121665Z  headers = { 2025-08-14T20:43:47.7122206Z  "Accept": "application/vnd.github.v3+json", 2025-08-14T20:43:47.7122873Z  "Authorization": f"token {github_token}", 2025-08-14T20:43:47.7123446Z  } 2025-08-14T20:43:47.7123942Z  json_response: dict[str, Any] = download_json( 2025-08-14T20:43:47.7124600Z  url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:43:47.7125180Z  headers=headers, 2025-08-14T20:43:47.7125674Z  ) 2025-08-14T20:43:47.7126056Z  2025-08-14T20:43:47.7126461Z  if not json_response: 2025-08-14T20:43:47.7127110Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:43:47.7127924Z  return {} 2025-08-14T20:43:47.7128375Z  2025-08-14T20:43:47.7128773Z  return json_response 2025-08-14T20:43:47.7129259Z  2025-08-14T20:43:47.7129625Z  2025-08-14T20:43:47.7130268Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:43:47.7131031Z  """ 2025-08-14T20:43:47.7131728Z  Dynamically get the latest list of labels from the pull request 2025-08-14T20:43:47.7132422Z  """ 2025-08-14T20:43:47.7132981Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:43:47.7133638Z  return { 2025-08-14T20:43:47.7134293Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:43:47.7135027Z  } 2025-08-14T20:43:47.7135415Z  2025-08-14T20:43:47.7135784Z  2025-08-14T20:43:47.7136170Z def main() -> None: 2025-08-14T20:43:47.7136666Z  args = parse_args() 2025-08-14T20:43:47.7137146Z  2025-08-14T20:43:47.7137618Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:43:47.7138186Z  2025-08-14T20:43:47.7138596Z  # Check if the PR is opt-out 2025-08-14T20:43:47.7139149Z  if args.pr_number: 2025-08-14T20:43:47.7139899Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:43:47.7140707Z  if OPT_OUT_LABEL in labels: 2025-08-14T20:43:47.7141248Z  log.info( 2025-08-14T20:43:47.7142122Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:43:47.7142925Z  ) 2025-08-14T20:43:47.7143560Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.7144282Z  sys.exit() 2025-08-14T20:43:47.7144870Z  2025-08-14T20:43:47.7145245Z  try: 2025-08-14T20:43:47.7145743Z  rollout_state = get_rollout_state_from_issue( 2025-08-14T20:43:47.7146522Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:43:47.7147194Z  ) 2025-08-14T20:43:47.7147597Z  2025-08-14T20:43:47.7148036Z  username = get_potential_pr_author( 2025-08-14T20:43:47.7148623Z  args.github_token, 2025-08-14T20:43:47.7149167Z  args.github_repo, 2025-08-14T20:43:47.7149708Z  args.github_actor, 2025-08-14T20:43:47.7150261Z  args.github_ref_type, 2025-08-14T20:43:47.7150812Z  args.github_branch, 2025-08-14T20:43:47.7151329Z  ) 2025-08-14T20:43:47.7151835Z  2025-08-14T20:43:47.7152361Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:43:47.7153014Z  2025-08-14T20:43:47.7153462Z  runner_label_prefix = get_runner_prefix( 2025-08-14T20:43:47.7154064Z  rollout_state, 2025-08-14T20:43:47.7154628Z  (args.github_issue_owner, username), 2025-08-14T20:43:47.7155220Z  args.github_branch, 2025-08-14T20:43:47.7155777Z  args.eligible_experiments, 2025-08-14T20:43:47.7156367Z  args.opt_out_experiments, 2025-08-14T20:43:47.7156917Z  is_canary, 2025-08-14T20:43:47.7157380Z  ) 2025-08-14T20:43:47.7157785Z  2025-08-14T20:43:47.7158187Z  except Exception as e: 2025-08-14T20:43:47.7158703Z  log.error( 2025-08-14T20:43:47.7159460Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:43:47.7160257Z  ) 2025-08-14T20:43:47.7160661Z  2025-08-14T20:43:47.7161362Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.7162165Z  2025-08-14T20:43:47.7162535Z  2025-08-14T20:43:47.7162932Z if __name__ == "__main__": 2025-08-14T20:43:47.7163423Z  main() 2025-08-14T20:43:47.7163831Z  2025-08-14T20:43:47.7164195Z EOF 2025-08-14T20:43:47.7164576Z  2025-08-14T20:43:47.7164983Z cat runner_determinator.py 2025-08-14T20:43:47.7419266Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:47.7420118Z env: 2025-08-14T20:43:47.7420887Z GITHUB_TOKEN: *** 2025-08-14T20:43:47.7421359Z ISSUE_NUMBER: 5132 2025-08-14T20:43:47.7422033Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:47.7422613Z ISSUE_OWNER: 2025-08-14T20:43:47.7423058Z CHECK_EXPERIMENTS: 2025-08-14T20:43:47.7423535Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:43:47.7424012Z PR_NUMBER: 2025-08-14T20:43:47.7424438Z ##[endgroup] 2025-08-14T20:43:47.7639813Z # flake8: noqa: G004 2025-08-14T20:43:47.7640173Z 2025-08-14T20:43:47.7640610Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:43:47.7641815Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:43:47.7642671Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:43:47.7643124Z 2025-08-14T20:43:47.7643287Z """ 2025-08-14T20:43:47.7643864Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:43:47.7644739Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:43:47.7645663Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:43:47.7646489Z of which runners should be used to run which job. 2025-08-14T20:43:47.7646893Z 2025-08-14T20:43:47.7647282Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:43:47.7648377Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:43:47.7649293Z settings are considered to be empty with only the second part, the user 2025-08-14T20:43:47.7650004Z list, defined. 2025-08-14T20:43:47.7650236Z 2025-08-14T20:43:47.7650597Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:43:47.7651768Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:43:47.7652638Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:43:47.7653086Z 2025-08-14T20:43:47.7653455Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:43:47.7654318Z The user list is also a comma separated list of additional features or 2025-08-14T20:43:47.7655057Z experiments which the user could be opted in to. 2025-08-14T20:43:47.7655464Z 2025-08-14T20:43:47.7655656Z The user list has the following rules: 2025-08-14T20:43:47.7656009Z 2025-08-14T20:43:47.7656327Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:43:47.7657184Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:43:47.7657945Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:43:47.7658332Z 2025-08-14T20:43:47.7658505Z Example config: 2025-08-14T20:43:47.7658949Z # A list of experiments that can be opted into. 2025-08-14T20:43:47.7659628Z # This defines the behavior they'll induce when opted into. 2025-08-14T20:43:47.7660252Z # Expected syntax is: 2025-08-14T20:43:47.7660911Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:43:47.7662106Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:43:47.7662733Z 2025-08-14T20:43:47.7662908Z experiments: 2025-08-14T20:43:47.7663298Z lf: 2025-08-14T20:43:47.7663676Z rollout_percent: 25 2025-08-14T20:43:47.7664126Z all_branches: false 2025-08-14T20:43:47.7664810Z default: true 2025-08-14T20:43:47.7665228Z --- 2025-08-14T20:43:47.7665433Z 2025-08-14T20:43:47.7665594Z # Opt-ins: 2025-08-14T20:43:47.7666174Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:43:47.7667040Z # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:43:47.7667831Z # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:43:47.7668503Z # Experiments should be from the above list. 2025-08-14T20:43:47.7668888Z 2025-08-14T20:43:47.7669071Z @User1,-lf,split_build 2025-08-14T20:43:47.7669520Z @User2,lf 2025-08-14T20:43:47.7669914Z @User3,split_build 2025-08-14T20:43:47.7670327Z """ 2025-08-14T20:43:47.7670521Z 2025-08-14T20:43:47.7670685Z import json 2025-08-14T20:43:47.7671112Z import logging 2025-08-14T20:43:47.7671762Z import os 2025-08-14T20:43:47.7672172Z import random 2025-08-14T20:43:47.7672556Z import re 2025-08-14T20:43:47.7672921Z import sys 2025-08-14T20:43:47.7673341Z from argparse import ArgumentParser 2025-08-14T20:43:47.7673882Z from collections.abc import Iterable 2025-08-14T20:43:47.7674406Z from functools import cache 2025-08-14T20:43:47.7674877Z from logging import LogRecord 2025-08-14T20:43:47.7675371Z from typing import Any, NamedTuple 2025-08-14T20:43:47.7675901Z from urllib.request import Request, urlopen 2025-08-14T20:43:47.7676277Z 2025-08-14T20:43:47.7676442Z import yaml 2025-08-14T20:43:47.7676836Z from github import Auth, Github 2025-08-14T20:43:47.7677329Z from github.Issue import Issue 2025-08-14T20:43:47.7677632Z 2025-08-14T20:43:47.7677638Z 2025-08-14T20:43:47.7677880Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:43:47.7678573Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:43:47.7679438Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:43:47.7680011Z 2025-08-14T20:43:47.7680241Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:43:47.7680967Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:43:47.7681690Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:43:47.7682286Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:43:47.7682645Z 2025-08-14T20:43:47.7682843Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:43:47.7683180Z 2025-08-14T20:43:47.7683369Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:43:47.7683837Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:43:47.7684120Z 2025-08-14T20:43:47.7684126Z 2025-08-14T20:43:47.7684311Z class Experiment(NamedTuple): 2025-08-14T20:43:47.7684785Z rollout_perc: float = ( 2025-08-14T20:43:47.7685416Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:43:47.7686115Z ) 2025-08-14T20:43:47.7686488Z all_branches: bool = ( 2025-08-14T20:43:47.7687110Z False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:43:47.7687787Z ) 2025-08-14T20:43:47.7688140Z default: bool = ( 2025-08-14T20:43:47.7688715Z True # If True, the experiment is enabled by default for all queries 2025-08-14T20:43:47.7689351Z ) 2025-08-14T20:43:47.7689549Z 2025-08-14T20:43:47.7689726Z # Add more fields as needed 2025-08-14T20:43:47.7690027Z 2025-08-14T20:43:47.7690033Z 2025-08-14T20:43:47.7690225Z class Settings(NamedTuple): 2025-08-14T20:43:47.7690665Z """ 2025-08-14T20:43:47.7691123Z Settings for the experiments that can be opted into. 2025-08-14T20:43:47.7691866Z """ 2025-08-14T20:43:47.7692066Z 2025-08-14T20:43:47.7692276Z experiments: dict[str, Experiment] = {} 2025-08-14T20:43:47.7692650Z 2025-08-14T20:43:47.7692658Z 2025-08-14T20:43:47.7692890Z class ColorFormatter(logging.Formatter): 2025-08-14T20:43:47.7693538Z """Color codes the log messages based on the log level""" 2025-08-14T20:43:47.7693971Z 2025-08-14T20:43:47.7694136Z COLORS = { 2025-08-14T20:43:47.7694529Z "WARNING": "\033[33m", # Yellow 2025-08-14T20:43:47.7695041Z "ERROR": "\033[31m", # Red 2025-08-14T20:43:47.7695686Z "CRITICAL": "\033[31m", # Red 2025-08-14T20:43:47.7696189Z "INFO": "\033[0m", # Reset 2025-08-14T20:43:47.7696664Z "DEBUG": "\033[0m", # Reset 2025-08-14T20:43:47.7697131Z } 2025-08-14T20:43:47.7697325Z 2025-08-14T20:43:47.7697546Z def format(self, record: LogRecord) -> str: 2025-08-14T20:43:47.7698314Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:43:47.7699093Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:43:47.7699666Z return super().format(record) 2025-08-14T20:43:47.7699996Z 2025-08-14T20:43:47.7700003Z 2025-08-14T20:43:47.7700202Z handler = logging.StreamHandler() 2025-08-14T20:43:47.7700893Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:43:47.7701607Z 2025-08-14T20:43:47.7701870Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:43:47.7702453Z log.addHandler(handler) 2025-08-14T20:43:47.7702904Z log.setLevel(logging.INFO) 2025-08-14T20:43:47.7703204Z 2025-08-14T20:43:47.7703210Z 2025-08-14T20:43:47.7703470Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:43:47.7704238Z """ 2025-08-14T20:43:47.7705105Z Defines outputs of the github action that invokes this script 2025-08-14T20:43:47.7706200Z """ 2025-08-14T20:43:47.7706944Z if not GITHUB_OUTPUT: 2025-08-14T20:43:47.7709047Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:43:47.7711229Z log.warning( 2025-08-14T20:43:47.7712979Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:43:47.7714537Z ) 2025-08-14T20:43:47.7729056Z print(f"::set-output name={key}::{value}") 2025-08-14T20:43:47.7730123Z return 2025-08-14T20:43:47.7730586Z 2025-08-14T20:43:47.7731215Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:43:47.7732645Z log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:43:47.7733679Z f.write(f"{key}={value}\n") 2025-08-14T20:43:47.7734266Z 2025-08-14T20:43:47.7734279Z 2025-08-14T20:43:47.7734836Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:43:47.7736055Z return frozenset( 2025-08-14T20:43:47.7737232Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:43:47.7738497Z ) 2025-08-14T20:43:47.7738849Z 2025-08-14T20:43:47.7738860Z 2025-08-14T20:43:47.7739099Z def parse_args() -> Any: 2025-08-14T20:43:47.7739699Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:43:47.7740597Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:43:47.7741381Z parser.add_argument( 2025-08-14T20:43:47.7742123Z "--github-issue-repo", 2025-08-14T20:43:47.7742609Z type=str, 2025-08-14T20:43:47.7743043Z required=False, 2025-08-14T20:43:47.7743512Z default="pytorch/test-infra", 2025-08-14T20:43:47.7744062Z help="GitHub repo to get the issue", 2025-08-14T20:43:47.7744586Z ) 2025-08-14T20:43:47.7744954Z parser.add_argument( 2025-08-14T20:43:47.7745411Z "--github-repo", 2025-08-14T20:43:47.7745844Z type=str, 2025-08-14T20:43:47.7746244Z required=True, 2025-08-14T20:43:47.7746710Z help="GitHub repo where CI is running", 2025-08-14T20:43:47.7747234Z ) 2025-08-14T20:43:47.7747616Z parser.add_argument( 2025-08-14T20:43:47.7748224Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:43:47.7800942Z ) 2025-08-14T20:43:47.7801716Z parser.add_argument( 2025-08-14T20:43:47.7802485Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:43:47.7803196Z ) 2025-08-14T20:43:47.7803604Z parser.add_argument( 2025-08-14T20:43:47.7804274Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:43:47.7805193Z ) 2025-08-14T20:43:47.7805565Z parser.add_argument( 2025-08-14T20:43:47.7806230Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:43:47.7806958Z ) 2025-08-14T20:43:47.7807321Z parser.add_argument( 2025-08-14T20:43:47.7807773Z "--github-ref-type", 2025-08-14T20:43:47.7808222Z type=str, 2025-08-14T20:43:47.7808619Z required=True, 2025-08-14T20:43:47.7809102Z help="Current GitHub ref type, branch or tag", 2025-08-14T20:43:47.7809652Z ) 2025-08-14T20:43:47.7810008Z parser.add_argument( 2025-08-14T20:43:47.7810464Z "--eligible-experiments", 2025-08-14T20:43:47.7810965Z type=_str_comma_separated_to_set, 2025-08-14T20:43:47.7811662Z required=False, 2025-08-14T20:43:47.7812166Z default="", 2025-08-14T20:43:47.7813024Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:43:47.7813953Z ) 2025-08-14T20:43:47.7814320Z parser.add_argument( 2025-08-14T20:43:47.7814776Z "--opt-out-experiments", 2025-08-14T20:43:47.7815275Z type=_str_comma_separated_to_set, 2025-08-14T20:43:47.7815793Z required=False, 2025-08-14T20:43:47.7816200Z default="", 2025-08-14T20:43:47.7816590Z help=( 2025-08-14T20:43:47.7817235Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:43:47.7818360Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:43:47.7819185Z ), 2025-08-14T20:43:47.7819529Z ) 2025-08-14T20:43:47.7819903Z parser.add_argument( 2025-08-14T20:43:47.7820333Z "--pr-number", 2025-08-14T20:43:47.7820743Z type=str, 2025-08-14T20:43:47.7821177Z required=False, 2025-08-14T20:43:47.7821802Z default="", 2025-08-14T20:43:47.7822455Z help="the optional PR number where this is run", 2025-08-14T20:43:47.7823018Z ) 2025-08-14T20:43:47.7823213Z 2025-08-14T20:43:47.7823401Z return parser.parse_args() 2025-08-14T20:43:47.7823705Z 2025-08-14T20:43:47.7823712Z 2025-08-14T20:43:47.7824109Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.7824869Z auth = Auth.Token(github_token) 2025-08-14T20:43:47.7825364Z return Github(auth=auth) 2025-08-14T20:43:47.7825666Z 2025-08-14T20:43:47.7825673Z 2025-08-14T20:43:47.7826119Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.7826915Z repo = gh.get_repo(repo) 2025-08-14T20:43:47.7827404Z return repo.get_issue(number=issue_num) 2025-08-14T20:43:47.7827769Z 2025-08-14T20:43:47.7827776Z 2025-08-14T20:43:47.7827970Z def get_potential_pr_author( 2025-08-14T20:43:47.7828600Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:43:47.7829284Z ) -> str: 2025-08-14T20:43:47.7829795Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:43:47.7830597Z # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:43:47.7831329Z # embedded in the tag name: ciflow// 2025-08-14T20:43:47.7831867Z 2025-08-14T20:43:47.7832058Z gh = get_gh_client(github_token) 2025-08-14T20:43:47.7832390Z 2025-08-14T20:43:47.7832662Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:43:47.7833274Z split_tag = ref_name.split("/") 2025-08-14T20:43:47.7833766Z if ( 2025-08-14T20:43:47.7834145Z len(split_tag) == 3 2025-08-14T20:43:47.7834618Z and split_tag[0] == "ciflow" 2025-08-14T20:43:47.7835135Z and split_tag[2].isnumeric() 2025-08-14T20:43:47.7835623Z ): 2025-08-14T20:43:47.7836002Z pr_number = split_tag[2] 2025-08-14T20:43:47.7836475Z try: 2025-08-14T20:43:47.7837034Z repository = gh.get_repo(repo) 2025-08-14T20:43:47.7837631Z pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:43:47.7838236Z except Exception as e: 2025-08-14T20:43:47.7838738Z raise Exception( # noqa: TRY002 2025-08-14T20:43:47.7839401Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:43:47.7840027Z ) from e 2025-08-14T20:43:47.7840553Z return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:43:47.7841241Z # In all other cases, return the original input username 2025-08-14T20:43:47.7841958Z return username 2025-08-14T20:43:47.7842192Z 2025-08-14T20:43:47.7842199Z 2025-08-14T20:43:47.7842420Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:43:47.7842942Z """ 2025-08-14T20:43:47.7843570Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:43:47.7844346Z """ 2025-08-14T20:43:47.7844898Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:43:47.7845422Z 2025-08-14T20:43:47.7845429Z 2025-08-14T20:43:47.7845623Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:43:47.7846118Z try: 2025-08-14T20:43:47.7846551Z data = yaml.safe_load(yaml_text) 2025-08-14T20:43:47.7847048Z return data 2025-08-14T20:43:47.7847461Z except yaml.YAMLError: 2025-08-14T20:43:47.7847929Z log.exception("Error loading YAML") 2025-08-14T20:43:47.7848439Z raise 2025-08-14T20:43:47.7848650Z 2025-08-14T20:43:47.7848657Z 2025-08-14T20:43:47.7849069Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:43:47.7849810Z """ 2025-08-14T20:43:47.7850424Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:43:47.7851023Z 2025-08-14T20:43:47.7851652Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.7852446Z and the text below is the list of opted in users. 2025-08-14T20:43:47.7852853Z 2025-08-14T20:43:47.7853219Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:43:47.7853917Z """ 2025-08-14T20:43:47.7854356Z rollout_state_parts = rollout_state.split("---") 2025-08-14T20:43:47.7854959Z if len(rollout_state_parts) >= 2: 2025-08-14T20:43:47.7855558Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:43:47.7856142Z else: 2025-08-14T20:43:47.7856519Z return "", rollout_state 2025-08-14T20:43:47.7856825Z 2025-08-14T20:43:47.7856833Z 2025-08-14T20:43:47.7857032Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:43:47.7857535Z """ 2025-08-14T20:43:47.7858037Z Dictionary of users with a list of features they have opted into 2025-08-14T20:43:47.7858677Z """ 2025-08-14T20:43:47.7858875Z 2025-08-14T20:43:47.7858881Z 2025-08-14T20:43:47.7859233Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:43:47.7859873Z """ 2025-08-14T20:43:47.7860574Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:43:47.7861253Z 2025-08-14T20:43:47.7861998Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:43:47.7863000Z - Example line: "@User1,lf,split_build" 2025-08-14T20:43:47.7863697Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:43:47.7864180Z 2025-08-14T20:43:47.7864186Z 2025-08-14T20:43:47.7864340Z """ 2025-08-14T20:43:47.7864706Z optins = UserOptins() 2025-08-14T20:43:47.7865197Z for user in user_optin_text.split("\n"): 2025-08-14T20:43:47.7865751Z user = user.strip("\r\n\t -") 2025-08-14T20:43:47.7866288Z if not user or not user.startswith("@"): 2025-08-14T20:43:47.7866846Z # Not a valid user. Skip 2025-08-14T20:43:47.7867461Z continue 2025-08-14T20:43:47.7867709Z 2025-08-14T20:43:47.7867868Z if user: 2025-08-14T20:43:47.7868298Z usr_name = user.split(",")[0].strip("@") 2025-08-14T20:43:47.7868986Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:43:47.7869472Z 2025-08-14T20:43:47.7869642Z return optins 2025-08-14T20:43:47.7869875Z 2025-08-14T20:43:47.7869882Z 2025-08-14T20:43:47.7870163Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:43:47.7870761Z """ 2025-08-14T20:43:47.7871149Z Check if the experiment name is valid. 2025-08-14T20:43:47.7871771Z A valid name: 2025-08-14T20:43:47.7872402Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:43:47.7873355Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:43:47.7874084Z - Cannot contain spaces 2025-08-14T20:43:47.7874540Z """ 2025-08-14T20:43:47.7874742Z 2025-08-14T20:43:47.7875002Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:43:47.7875697Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:43:47.7876147Z 2025-08-14T20:43:47.7876302Z if valid: 2025-08-14T20:43:47.7876675Z return True 2025-08-14T20:43:47.7876909Z 2025-08-14T20:43:47.7877067Z log.error( 2025-08-14T20:43:47.7878509Z 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-14T20:43:47.7880106Z ) 2025-08-14T20:43:47.7880455Z return False 2025-08-14T20:43:47.7880687Z 2025-08-14T20:43:47.7880693Z 2025-08-14T20:43:47.7880996Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:43:47.7881718Z """ 2025-08-14T20:43:47.7882429Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:43:47.7883167Z """ 2025-08-14T20:43:47.7883522Z try: 2025-08-14T20:43:47.7883886Z if settings_text: 2025-08-14T20:43:47.7884614Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:43:47.7885406Z # for easy reading 2025-08-14T20:43:47.7886173Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:43:47.7887062Z # the backtick character in shell commands. 2025-08-14T20:43:47.7887666Z backtick = chr(96) # backtick character 2025-08-14T20:43:47.7888323Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:43:47.7888983Z settings = load_yaml(settings_text) 2025-08-14T20:43:47.7889366Z 2025-08-14T20:43:47.7889774Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:43:47.7890541Z experiments = {} 2025-08-14T20:43:47.7890834Z 2025-08-14T20:43:47.7891205Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:43:47.7892129Z if not is_valid_experiment_name(exp_name): 2025-08-14T20:43:47.7893235Z # 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-14T20:43:47.7894284Z continue 2025-08-14T20:43:47.7894569Z 2025-08-14T20:43:47.7894754Z valid_settings = {} 2025-08-14T20:43:47.7895269Z for setting in exp_settings: 2025-08-14T20:43:47.7895837Z if setting not in Experiment._fields: 2025-08-14T20:43:47.7896386Z log.warning( 2025-08-14T20:43:47.7897092Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:43:47.7897819Z ) 2025-08-14T20:43:47.7898390Z else: 2025-08-14T20:43:47.7898896Z valid_settings[setting] = exp_settings[setting] 2025-08-14T20:43:47.7899327Z 2025-08-14T20:43:47.7899601Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:43:47.7900235Z return Settings(experiments) 2025-08-14T20:43:47.7900590Z 2025-08-14T20:43:47.7900761Z except Exception: 2025-08-14T20:43:47.7901235Z log.exception("Failed to parse settings") 2025-08-14T20:43:47.7901769Z 2025-08-14T20:43:47.7901938Z return Settings() 2025-08-14T20:43:47.7902203Z 2025-08-14T20:43:47.7902210Z 2025-08-14T20:43:47.7902453Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:43:47.7903014Z """ 2025-08-14T20:43:47.7903449Z Parse settings, if any, from the rollout state. 2025-08-14T20:43:47.7903855Z 2025-08-14T20:43:47.7904206Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.7904975Z and the text below is the list of opted in users. 2025-08-14T20:43:47.7905385Z 2025-08-14T20:43:47.7905789Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:43:47.7906520Z """ 2025-08-14T20:43:47.7907069Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.7907825Z return parse_settings_from_text(settings_text) 2025-08-14T20:43:47.7908220Z 2025-08-14T20:43:47.7908227Z 2025-08-14T20:43:47.7908473Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:43:47.7909037Z """ 2025-08-14T20:43:47.7909413Z Parse users from the rollout state. 2025-08-14T20:43:47.7909766Z 2025-08-14T20:43:47.7909918Z """ 2025-08-14T20:43:47.7910433Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.7911184Z return parse_user_opt_in_from_text(users_text) 2025-08-14T20:43:47.7911686Z 2025-08-14T20:43:47.7911693Z 2025-08-14T20:43:47.7912249Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.7913004Z """ 2025-08-14T20:43:47.7913424Z Check if a user is opted into an experiment 2025-08-14T20:43:47.7913958Z """ 2025-08-14T20:43:47.7914412Z return experiment_name in user_optins.get(user, []) 2025-08-14T20:43:47.7914831Z 2025-08-14T20:43:47.7914837Z 2025-08-14T20:43:47.7915259Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.7916006Z """ 2025-08-14T20:43:47.7916464Z Check if a user explicitly opted out of an experiment 2025-08-14T20:43:47.7917044Z """ 2025-08-14T20:43:47.7917550Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:43:47.7918227Z experiment_optout = "-" + experiment_name 2025-08-14T20:43:47.7918875Z if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:43:47.7919475Z return False 2025-08-14T20:43:47.7919738Z 2025-08-14T20:43:47.7920020Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:43:47.7920630Z log.warning( 2025-08-14T20:43:47.7921422Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:43:47.7922421Z ) 2025-08-14T20:43:47.7922631Z 2025-08-14T20:43:47.7922794Z return True 2025-08-14T20:43:47.7923032Z 2025-08-14T20:43:47.7923038Z 2025-08-14T20:43:47.7923218Z def get_runner_prefix( 2025-08-14T20:43:47.7923662Z rollout_state: str, 2025-08-14T20:43:47.7924118Z workflow_requestors: Iterable[str], 2025-08-14T20:43:47.7924639Z branch: str, 2025-08-14T20:43:47.7925123Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.7925794Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.7926381Z is_canary: bool = False, 2025-08-14T20:43:47.7926834Z ) -> str: 2025-08-14T20:43:47.7927242Z settings = parse_settings(rollout_state) 2025-08-14T20:43:47.7927968Z user_optins = parse_users(rollout_state) 2025-08-14T20:43:47.7928339Z 2025-08-14T20:43:47.7928513Z fleet_prefix = "" 2025-08-14T20:43:47.7928937Z prefixes = [] 2025-08-14T20:43:47.7929557Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:43:47.7930481Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:43:47.7931193Z log.info( 2025-08-14T20:43:47.7932022Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:43:47.7932794Z ) 2025-08-14T20:43:47.7933171Z continue 2025-08-14T20:43:47.7933426Z 2025-08-14T20:43:47.7933611Z if opt_out_experiments: 2025-08-14T20:43:47.7934153Z if experiment_name in opt_out_experiments: 2025-08-14T20:43:47.7934791Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:43:47.7935383Z log.info( 2025-08-14T20:43:47.7936317Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:43:47.7937299Z ) 2025-08-14T20:43:47.7937692Z continue 2025-08-14T20:43:47.7937965Z 2025-08-14T20:43:47.7938149Z if eligible_experiments: 2025-08-14T20:43:47.7938732Z if experiment_name not in eligible_experiments: 2025-08-14T20:43:47.7939367Z exp_list = ", ".join(eligible_experiments) 2025-08-14T20:43:47.7939926Z log.info( 2025-08-14T20:43:47.7940698Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:43:47.7941666Z ) 2025-08-14T20:43:47.7942060Z continue 2025-08-14T20:43:47.7942534Z elif not experiment_settings.default: 2025-08-14T20:43:47.7943067Z log.info( 2025-08-14T20:43:47.7943842Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:43:47.7944596Z ) 2025-08-14T20:43:47.7944965Z continue 2025-08-14T20:43:47.7945207Z 2025-08-14T20:43:47.7945483Z # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:43:47.7946102Z opted_out_users = [ 2025-08-14T20:43:47.7946546Z requestor 2025-08-14T20:43:47.7946992Z for requestor in workflow_requestors 2025-08-14T20:43:47.7947656Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.7948276Z ] 2025-08-14T20:43:47.7948485Z 2025-08-14T20:43:47.7948660Z if opted_out_users: 2025-08-14T20:43:47.7949109Z log.info( 2025-08-14T20:43:47.7949716Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:43:47.7950419Z ) 2025-08-14T20:43:47.7950793Z continue 2025-08-14T20:43:47.7951044Z 2025-08-14T20:43:47.7951361Z # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:43:47.7952108Z opted_in_users = [ 2025-08-14T20:43:47.7952561Z requestor 2025-08-14T20:43:47.7953013Z for requestor in workflow_requestors 2025-08-14T20:43:47.7953684Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.7954310Z ] 2025-08-14T20:43:47.7954509Z 2025-08-14T20:43:47.7954680Z enabled = False 2025-08-14T20:43:47.7955124Z if opted_in_users: 2025-08-14T20:43:47.7955567Z log.info( 2025-08-14T20:43:47.7956182Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:43:47.7956871Z ) 2025-08-14T20:43:47.7957257Z enabled = True 2025-08-14T20:43:47.7957540Z 2025-08-14T20:43:47.7957754Z elif experiment_settings.rollout_perc: 2025-08-14T20:43:47.7958595Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:43:47.7959676Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:43:47.7960331Z log.info( 2025-08-14T20:43:47.7961199Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:43:47.7962232Z ) 2025-08-14T20:43:47.7962640Z enabled = True 2025-08-14T20:43:47.7962943Z 2025-08-14T20:43:47.7963108Z if enabled: 2025-08-14T20:43:47.7963527Z label = experiment_name 2025-08-14T20:43:47.7964083Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:43:47.7964916Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:43:47.7965819Z # - If it's enabled, then we always list it's prefix first 2025-08-14T20:43:47.7966580Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:43:47.7967256Z if is_canary: 2025-08-14T20:43:47.7967750Z label += CANARY_FLEET_SUFFIX 2025-08-14T20:43:47.7968306Z fleet_prefix = label 2025-08-14T20:43:47.7968796Z else: 2025-08-14T20:43:47.7969213Z prefixes.append(label) 2025-08-14T20:43:47.7969561Z 2025-08-14T20:43:47.7969753Z if len(prefixes) > 1: 2025-08-14T20:43:47.7970186Z log.error( 2025-08-14T20:43:47.7971219Z 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-14T20:43:47.7972882Z ) 2025-08-14T20:43:47.7973287Z prefixes = prefixes[:1] 2025-08-14T20:43:47.7973602Z 2025-08-14T20:43:47.7973795Z # Fleet always comes first 2025-08-14T20:43:47.7974267Z if fleet_prefix: 2025-08-14T20:43:47.7974715Z prefixes.insert(0, fleet_prefix) 2025-08-14T20:43:47.7975074Z 2025-08-14T20:43:47.7975471Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:43:47.7975903Z 2025-08-14T20:43:47.7975909Z 2025-08-14T20:43:47.7976360Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:43:47.7977122Z """ 2025-08-14T20:43:47.7977707Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:43:47.7978269Z 2025-08-14T20:43:47.7978656Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:43:47.7979365Z """ 2025-08-14T20:43:47.7979750Z gh = get_gh_client(github_token) 2025-08-14T20:43:47.7980287Z issue = get_issue(gh, repo, issue_num) 2025-08-14T20:43:47.7980926Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:43:47.7981372Z 2025-08-14T20:43:47.7981379Z 2025-08-14T20:43:47.7981933Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:43:47.7982707Z for _ in range(num_retries): 2025-08-14T20:43:47.7983202Z try: 2025-08-14T20:43:47.7983620Z req = Request(url=url, headers=headers) 2025-08-14T20:43:47.7984283Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:43:47.7984924Z return json.loads(content) 2025-08-14T20:43:47.7985457Z except Exception as e: 2025-08-14T20:43:47.7985989Z log.warning(f"Could not download {url}: {e}") 2025-08-14T20:43:47.7986404Z 2025-08-14T20:43:47.7986774Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:43:47.7987488Z return {} 2025-08-14T20:43:47.7987709Z 2025-08-14T20:43:47.7987716Z 2025-08-14T20:43:47.7987872Z @cache 2025-08-14T20:43:47.7988492Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:43:47.7989241Z """ 2025-08-14T20:43:47.7989634Z Dynamically get PR information 2025-08-14T20:43:47.7990117Z """ 2025-08-14T20:43:47.7990611Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:43:47.7991367Z headers = { 2025-08-14T20:43:47.7991936Z "Accept": "application/vnd.github.v3+json", 2025-08-14T20:43:47.7992538Z "Authorization": f"token {github_token}", 2025-08-14T20:43:47.7993070Z } 2025-08-14T20:43:47.7993494Z json_response: dict[str, Any] = download_json( 2025-08-14T20:43:47.7994095Z url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:43:47.7994645Z headers=headers, 2025-08-14T20:43:47.7995058Z ) 2025-08-14T20:43:47.7995260Z 2025-08-14T20:43:47.7995441Z if not json_response: 2025-08-14T20:43:47.7996004Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:43:47.7996631Z return {} 2025-08-14T20:43:47.7996866Z 2025-08-14T20:43:47.7997046Z return json_response 2025-08-14T20:43:47.7997321Z 2025-08-14T20:43:47.7997328Z 2025-08-14T20:43:47.7997721Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:43:47.7998469Z """ 2025-08-14T20:43:47.7998982Z Dynamically get the latest list of labels from the pull request 2025-08-14T20:43:47.7999638Z """ 2025-08-14T20:43:47.8000115Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:43:47.8000735Z return { 2025-08-14T20:43:47.8001317Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:43:47.8002127Z } 2025-08-14T20:43:47.8002327Z 2025-08-14T20:43:47.8002333Z 2025-08-14T20:43:47.8002510Z def main() -> None: 2025-08-14T20:43:47.8002921Z args = parse_args() 2025-08-14T20:43:47.8003188Z 2025-08-14T20:43:47.8003412Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:43:47.8003790Z 2025-08-14T20:43:47.8003981Z # Check if the PR is opt-out 2025-08-14T20:43:47.8004470Z if args.pr_number: 2025-08-14T20:43:47.8005113Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:43:47.8005997Z if OPT_OUT_LABEL in labels: 2025-08-14T20:43:47.8006509Z log.info( 2025-08-14T20:43:47.8007187Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:43:47.8007955Z ) 2025-08-14T20:43:47.8008494Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.8009172Z sys.exit() 2025-08-14T20:43:47.8009429Z 2025-08-14T20:43:47.8009591Z try: 2025-08-14T20:43:47.8010024Z rollout_state = get_rollout_state_from_issue( 2025-08-14T20:43:47.8010730Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:43:47.8011363Z ) 2025-08-14T20:43:47.8011679Z 2025-08-14T20:43:47.8011890Z username = get_potential_pr_author( 2025-08-14T20:43:47.8012431Z args.github_token, 2025-08-14T20:43:47.8012912Z args.github_repo, 2025-08-14T20:43:47.8013388Z args.github_actor, 2025-08-14T20:43:47.8013869Z args.github_ref_type, 2025-08-14T20:43:47.8014374Z args.github_branch, 2025-08-14T20:43:47.8014838Z ) 2025-08-14T20:43:47.8015047Z 2025-08-14T20:43:47.8015332Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:43:47.8015783Z 2025-08-14T20:43:47.8015999Z runner_label_prefix = get_runner_prefix( 2025-08-14T20:43:47.8016556Z rollout_state, 2025-08-14T20:43:47.8017034Z (args.github_issue_owner, username), 2025-08-14T20:43:47.8017586Z args.github_branch, 2025-08-14T20:43:47.8018075Z args.eligible_experiments, 2025-08-14T20:43:47.8018612Z args.opt_out_experiments, 2025-08-14T20:43:47.8019110Z is_canary, 2025-08-14T20:43:47.8019520Z ) 2025-08-14T20:43:47.8019722Z 2025-08-14T20:43:47.8019908Z except Exception as e: 2025-08-14T20:43:47.8020353Z log.error( 2025-08-14T20:43:47.8021009Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:43:47.8022009Z ) 2025-08-14T20:43:47.8022216Z 2025-08-14T20:43:47.8022550Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.8023050Z 2025-08-14T20:43:47.8023057Z 2025-08-14T20:43:47.8023233Z if __name__ == "__main__": 2025-08-14T20:43:47.8023685Z main() 2025-08-14T20:43:47.8023891Z 2025-08-14T20:43:47.8114781Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:43:47.8115681Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:43:47.8156520Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:47.8156999Z env: 2025-08-14T20:43:47.8157647Z GITHUB_TOKEN: *** 2025-08-14T20:43:47.8158057Z ISSUE_NUMBER: 5132 2025-08-14T20:43:47.8158501Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:47.8159005Z ISSUE_OWNER: 2025-08-14T20:43:47.8159404Z CHECK_EXPERIMENTS: 2025-08-14T20:43:47.8159840Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:43:47.8160271Z PR_NUMBER: 2025-08-14T20:43:47.8160651Z ##[endgroup] 2025-08-14T20:43:48.1696956Z Defaulting to user installation because normal site-packages is not writeable 2025-08-14T20:43:48.5472997Z Collecting urllib3==1.26.18 2025-08-14T20:43:48.5909893Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-14T20:43:48.6153213Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.7 MB/s eta 0:00:00 2025-08-14T20:43:48.6388402Z Collecting PyGithub==2.3.0 2025-08-14T20:43:48.6441945Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-14T20:43:48.6889981Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-14T20:43:48.6927302Z 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-14T20:43:48.6971227Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-14T20:43:48.6989541Z 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-14T20:43:48.7005954Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-14T20:43:48.7259757Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-14T20:43:48.7291907Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-14T20:43:48.7523895Z 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-14T20:43:48.8654275Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:43:48.8720089Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-14T20:43:48.9884541Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-14T20:43:48.9925267Z 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-14T20:43:49.0180498Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:43:49.0251845Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-14T20:43:49.0483385Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-14T20:43:49.0582379Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 16.6 MB/s eta 0:00:00 2025-08-14T20:43:49.0616320Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-14T20:43:49.0719866Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 39.8 MB/s eta 0:00:00 2025-08-14T20:43:49.0753739Z 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-14T20:43:49.1203972Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 19.5 MB/s eta 0:00:00 2025-08-14T20:43:49.1238012Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-14T20:43:49.1296131Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-14T20:43:49.1388891Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 60.4 MB/s eta 0:00:00 2025-08-14T20:43:49.1426335Z 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-14T20:43:49.1465768Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 32.3 MB/s eta 0:00:00 2025-08-14T20:43:49.1509568Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-14T20:43:49.1550587Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 43.0 MB/s eta 0:00:00 2025-08-14T20:43:49.4332222Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-14T20:43:49.9533000Z 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-14T20:43:50.0300460Z ##[group]Run curr_branch="main" 2025-08-14T20:43:50.0300800Z curr_branch="main" 2025-08-14T20:43:50.0301020Z curr_ref_type="branch" 2025-08-14T20:43:50.0301281Z echo "Current branch is '$curr_branch'" 2025-08-14T20:43:50.0301791Z  2025-08-14T20:43:50.0301987Z python3 runner_determinator.py \ 2025-08-14T20:43:50.0302283Z  --github-token "$GITHUB_TOKEN" \ 2025-08-14T20:43:50.0302561Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-14T20:43:50.0302824Z  --github-branch "$curr_branch" \ 2025-08-14T20:43:50.0303087Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-14T20:43:50.0303374Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-14T20:43:50.0303650Z  --github-ref-type "$curr_ref_type" \ 2025-08-14T20:43:50.0303939Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-14T20:43:50.0304314Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-14T20:43:50.0304695Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-14T20:43:50.0305001Z  --pr-number "${PR_NUMBER}" 2025-08-14T20:43:50.0348959Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:50.0349214Z env: 2025-08-14T20:43:50.0349806Z GITHUB_TOKEN: *** 2025-08-14T20:43:50.0350000Z ISSUE_NUMBER: 5132 2025-08-14T20:43:50.0350221Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:50.0350464Z ISSUE_OWNER: 2025-08-14T20:43:50.0350648Z CHECK_EXPERIMENTS: 2025-08-14T20:43:50.0350843Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:43:50.0351035Z PR_NUMBER: 2025-08-14T20:43:50.0351209Z ##[endgroup] 2025-08-14T20:43:50.0412510Z Current branch is 'main' 2025-08-14T20:43:51.5448470Z INFO : Based on rollout percentage of 50%, enabling experiment lf. 2025-08-14T20:43:51.5450129Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-14T20:43:51.5451739Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-14T20:43:51.5453084Z INFO : Setting output: label-type='lf.' 2025-08-14T20:43:51.5781808Z Evaluate and set job outputs 2025-08-14T20:43:51.5788119Z Set output 'label-type' 2025-08-14T20:43:51.5789940Z Cleaning up orphan processes