2025-09-07T06:09:27.5139643Z Current runner version: '2.328.0' 2025-09-07T06:09:27.5161382Z ##[group]Runner Image Provisioner 2025-09-07T06:09:27.5162623Z Hosted Compute Agent 2025-09-07T06:09:27.5163267Z Version: 20250829.383 2025-09-07T06:09:27.5163966Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T06:09:27.5164795Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T06:09:27.5165416Z ##[endgroup] 2025-09-07T06:09:27.5166048Z ##[group]Operating System 2025-09-07T06:09:27.5166657Z Ubuntu 2025-09-07T06:09:27.5167214Z 24.04.3 2025-09-07T06:09:27.5167783Z LTS 2025-09-07T06:09:27.5168333Z ##[endgroup] 2025-09-07T06:09:27.5168896Z ##[group]Runner Image 2025-09-07T06:09:27.5169453Z Image: ubuntu-24.04 2025-09-07T06:09:27.5170096Z Version: 20250831.1.0 2025-09-07T06:09:27.5171139Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T06:09:27.5173289Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T06:09:27.5174359Z ##[endgroup] 2025-09-07T06:09:27.5176851Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T06:09:27.5179352Z Actions: read 2025-09-07T06:09:27.5179983Z Attestations: read 2025-09-07T06:09:27.5180645Z Checks: read 2025-09-07T06:09:27.5181216Z Contents: read 2025-09-07T06:09:27.5181756Z Deployments: read 2025-09-07T06:09:27.5182905Z Discussions: read 2025-09-07T06:09:27.5183468Z Issues: read 2025-09-07T06:09:27.5184028Z Metadata: read 2025-09-07T06:09:27.5184641Z Models: read 2025-09-07T06:09:27.5185174Z Packages: read 2025-09-07T06:09:27.5185778Z Pages: read 2025-09-07T06:09:27.5186342Z PullRequests: read 2025-09-07T06:09:27.5186962Z RepositoryProjects: read 2025-09-07T06:09:27.5187554Z SecurityEvents: read 2025-09-07T06:09:27.5188278Z Statuses: read 2025-09-07T06:09:27.5188885Z ##[endgroup] 2025-09-07T06:09:27.5190888Z Secret source: Actions 2025-09-07T06:09:27.5192204Z Prepare workflow directory 2025-09-07T06:09:27.5692771Z Prepare all required actions 2025-09-07T06:09:27.5748016Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T06:09:27.5752850Z ##[group] Inputs 2025-09-07T06:09:27.5753641Z check_experiments: 2025-09-07T06:09:27.5754275Z opt_out_experiments: 2025-09-07T06:09:27.5754966Z triggering_actor: pytorchmergebot 2025-09-07T06:09:27.5755614Z issue_owner: 2025-09-07T06:09:27.5756328Z curr_branch: main 2025-09-07T06:09:27.5756918Z curr_ref_type: branch 2025-09-07T06:09:27.5757546Z issue_number: 5132 2025-09-07T06:09:27.5758194Z ##[endgroup] 2025-09-07T06:09:27.5758825Z Complete job name: get-label-type / runner-determinator 2025-09-07T06:09:27.6540134Z ##[group]Run cat < runner_determinator.py 2025-09-07T06:09:27.6543486Z cat < runner_determinator.py 2025-09-07T06:09:27.6544555Z # flake8: noqa: G004 2025-09-07T06:09:27.6545533Z  2025-09-07T06:09:27.6546789Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:27.6548520Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:27.6550275Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:27.6551448Z  2025-09-07T06:09:27.6552311Z """ 2025-09-07T06:09:27.6553687Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:27.6555319Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:27.6557394Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:27.6558979Z of which runners should be used to run which job. 2025-09-07T06:09:27.6560103Z  2025-09-07T06:09:27.6561427Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:27.6563376Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:27.6565429Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:27.6566909Z list, defined. 2025-09-07T06:09:27.6567784Z  2025-09-07T06:09:27.6568889Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:27.6570780Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:27.6572729Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:27.6573878Z  2025-09-07T06:09:27.6575192Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:27.6576880Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:27.6578323Z experiments which the user could be opted in to. 2025-09-07T06:09:27.6579506Z  2025-09-07T06:09:27.6580291Z The user list has the following rules: 2025-09-07T06:09:27.6581364Z  2025-09-07T06:09:27.6582648Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:27.6584325Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:27.6585905Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:27.6587293Z  2025-09-07T06:09:27.6588047Z Example config: 2025-09-07T06:09:27.6588942Z  # A list of experiments that can be opted into. 2025-09-07T06:09:27.6590413Z  # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:27.6591608Z  # Expected syntax is: 2025-09-07T06:09:27.6593194Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:27.6595155Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:27.6596556Z  2025-09-07T06:09:27.6597396Z  experiments: 2025-09-07T06:09:27.6598240Z  lf: 2025-09-07T06:09:27.6599064Z  rollout_percent: 25 2025-09-07T06:09:27.6600122Z  all_branches: false 2025-09-07T06:09:27.6601087Z  default: true 2025-09-07T06:09:27.6602209Z  --- 2025-09-07T06:09:27.6603047Z  2025-09-07T06:09:27.6603846Z  # Opt-ins: 2025-09-07T06:09:27.6605119Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:27.6607171Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:27.6608754Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:27.6610077Z  # Experiments should be from the above list. 2025-09-07T06:09:27.6611295Z  2025-09-07T06:09:27.6612399Z  @User1,-lf,split_build 2025-09-07T06:09:27.6613370Z  @User2,lf 2025-09-07T06:09:27.6614328Z  @User3,split_build 2025-09-07T06:09:27.6615169Z """ 2025-09-07T06:09:27.6615932Z  2025-09-07T06:09:27.6616832Z import json 2025-09-07T06:09:27.6617663Z import logging 2025-09-07T06:09:27.6618425Z import os 2025-09-07T06:09:27.6619352Z import random 2025-09-07T06:09:27.6620234Z import re 2025-09-07T06:09:27.6620999Z import sys 2025-09-07T06:09:27.6622182Z from argparse import ArgumentParser 2025-09-07T06:09:27.6623340Z from collections.abc import Iterable 2025-09-07T06:09:27.6624459Z from functools import cache 2025-09-07T06:09:27.6625527Z from logging import LogRecord 2025-09-07T06:09:27.6626579Z from typing import Any, NamedTuple 2025-09-07T06:09:27.6627768Z from urllib.request import Request, urlopen 2025-09-07T06:09:27.6628944Z  2025-09-07T06:09:27.6629683Z import yaml 2025-09-07T06:09:27.6630824Z from github import Auth, Github 2025-09-07T06:09:27.6632180Z from github.Issue import Issue 2025-09-07T06:09:27.6633109Z  2025-09-07T06:09:27.6633816Z  2025-09-07T06:09:27.6634782Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:27.6636169Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:27.6637823Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:27.6639331Z  2025-09-07T06:09:27.6640233Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:27.6641323Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:27.6642706Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:27.6643901Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:27.6644845Z  2025-09-07T06:09:27.6645816Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:27.6646845Z  2025-09-07T06:09:27.6647591Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:27.6648665Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:27.6649645Z  2025-09-07T06:09:27.6650483Z  2025-09-07T06:09:27.6651255Z class Experiment(NamedTuple): 2025-09-07T06:09:27.6652475Z  rollout_perc: float = ( 2025-09-07T06:09:27.6653766Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:27.6655294Z  ) 2025-09-07T06:09:27.6656035Z  all_branches: bool = ( 2025-09-07T06:09:27.6657387Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:27.6658833Z  ) 2025-09-07T06:09:27.6659548Z  default: bool = ( 2025-09-07T06:09:27.6660877Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:27.6662389Z  ) 2025-09-07T06:09:27.6663149Z  2025-09-07T06:09:27.6663850Z  # Add more fields as needed 2025-09-07T06:09:27.6664997Z  2025-09-07T06:09:27.6665693Z  2025-09-07T06:09:27.6666433Z class Settings(NamedTuple): 2025-09-07T06:09:27.6667532Z  """ 2025-09-07T06:09:27.6668528Z  Settings for the experiments that can be opted into. 2025-09-07T06:09:27.6669781Z  """ 2025-09-07T06:09:27.6670498Z  2025-09-07T06:09:27.6671329Z  experiments: dict[str, Experiment] = {} 2025-09-07T06:09:27.6672553Z  2025-09-07T06:09:27.6673654Z  2025-09-07T06:09:27.6674511Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:27.6675773Z  """Color codes the log messages based on the log level""" 2025-09-07T06:09:27.6676988Z  2025-09-07T06:09:27.6677653Z  COLORS = { 2025-09-07T06:09:27.6757307Z  "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:27.6758359Z  "ERROR": "\033[31m", # Red 2025-09-07T06:09:27.6759277Z  "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:27.6760239Z  "INFO": "\033[0m", # Reset 2025-09-07T06:09:27.6761145Z  "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:27.6762212Z  } 2025-09-07T06:09:27.6762895Z  2025-09-07T06:09:27.6763670Z  def format(self, record: LogRecord) -> str: 2025-09-07T06:09:27.6765007Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:27.6766327Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:27.6767304Z  return super().format(record) 2025-09-07T06:09:27.6768122Z  2025-09-07T06:09:27.6768675Z  2025-09-07T06:09:27.6769322Z handler = logging.StreamHandler() 2025-09-07T06:09:27.6770589Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:27.6772329Z  2025-09-07T06:09:27.6773128Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:27.6774150Z log.addHandler(handler) 2025-09-07T06:09:27.6774935Z log.setLevel(logging.INFO) 2025-09-07T06:09:27.6775687Z  2025-09-07T06:09:27.6776220Z  2025-09-07T06:09:27.6776965Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:27.6777928Z  """ 2025-09-07T06:09:27.6778848Z  Defines outputs of the github action that invokes this script 2025-09-07T06:09:27.6779977Z  """ 2025-09-07T06:09:27.6780620Z  if not GITHUB_OUTPUT: 2025-09-07T06:09:27.6782686Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:27.6784648Z  log.warning( 2025-09-07T06:09:27.6786161Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T06:09:27.6787805Z  ) 2025-09-07T06:09:27.6788568Z  print(f"::set-output name={key}::{value}") 2025-09-07T06:09:27.6789495Z  return 2025-09-07T06:09:27.6790183Z  2025-09-07T06:09:27.6790873Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:27.6791854Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:27.6793010Z  f.write(f"{key}={value}\n") 2025-09-07T06:09:27.6793812Z  2025-09-07T06:09:27.6794365Z  2025-09-07T06:09:27.6795199Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:27.6796268Z  return frozenset( 2025-09-07T06:09:27.6797325Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:27.6798429Z  ) 2025-09-07T06:09:27.6799015Z  2025-09-07T06:09:27.6799563Z  2025-09-07T06:09:27.6800141Z def parse_args() -> Any: 2025-09-07T06:09:27.6801140Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:27.6802768Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:27.6804115Z  parser.add_argument( 2025-09-07T06:09:27.6804978Z  "--github-issue-repo", 2025-09-07T06:09:27.6805810Z  type=str, 2025-09-07T06:09:27.6806527Z  required=False, 2025-09-07T06:09:27.6807571Z  default="pytorch/test-infra", 2025-09-07T06:09:27.6808606Z  help="GitHub repo to get the issue", 2025-09-07T06:09:27.6809472Z  ) 2025-09-07T06:09:27.6810122Z  parser.add_argument( 2025-09-07T06:09:27.6810902Z  "--github-repo", 2025-09-07T06:09:27.6811662Z  type=str, 2025-09-07T06:09:27.6812526Z  required=True, 2025-09-07T06:09:27.6813358Z  help="GitHub repo where CI is running", 2025-09-07T06:09:27.6814252Z  ) 2025-09-07T06:09:27.6814914Z  parser.add_argument( 2025-09-07T06:09:27.6816057Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:27.6817221Z  ) 2025-09-07T06:09:27.6817854Z  parser.add_argument( 2025-09-07T06:09:27.6818994Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:27.6820201Z  ) 2025-09-07T06:09:27.6820858Z  parser.add_argument( 2025-09-07T06:09:27.6822141Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:27.6823344Z  ) 2025-09-07T06:09:27.6823973Z  parser.add_argument( 2025-09-07T06:09:27.6825112Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:27.6826527Z  ) 2025-09-07T06:09:27.6827149Z  parser.add_argument( 2025-09-07T06:09:27.6827965Z  "--github-ref-type", 2025-09-07T06:09:27.6828806Z  type=str, 2025-09-07T06:09:27.6829528Z  required=True, 2025-09-07T06:09:27.6830411Z  help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:27.6831357Z  ) 2025-09-07T06:09:27.6832226Z  parser.add_argument( 2025-09-07T06:09:27.6833103Z  "--eligible-experiments", 2025-09-07T06:09:27.6834037Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:27.6834935Z  required=False, 2025-09-07T06:09:27.6835731Z  default="", 2025-09-07T06:09:27.6837203Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:27.6838760Z  ) 2025-09-07T06:09:27.6839443Z  parser.add_argument( 2025-09-07T06:09:27.6840294Z  "--opt-out-experiments", 2025-09-07T06:09:27.6841199Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:27.6842271Z  required=False, 2025-09-07T06:09:27.6843099Z  default="", 2025-09-07T06:09:27.6843863Z  help=( 2025-09-07T06:09:27.6845044Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:27.6847021Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:27.6848463Z  ), 2025-09-07T06:09:27.6849101Z  ) 2025-09-07T06:09:27.6849726Z  parser.add_argument( 2025-09-07T06:09:27.6850508Z  "--pr-number", 2025-09-07T06:09:27.6851251Z  type=str, 2025-09-07T06:09:27.6851953Z  required=False, 2025-09-07T06:09:27.6852839Z  default="", 2025-09-07T06:09:27.6853689Z  help="the optional PR number where this is run", 2025-09-07T06:09:27.6854608Z  ) 2025-09-07T06:09:27.6855188Z  2025-09-07T06:09:27.6855813Z  return parser.parse_args() 2025-09-07T06:09:27.6856652Z  2025-09-07T06:09:27.6857239Z  2025-09-07T06:09:27.6858248Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:27.6859779Z  auth = Auth.Token(github_token) 2025-09-07T06:09:27.6860725Z  return Github(auth=auth) 2025-09-07T06:09:27.6861503Z  2025-09-07T06:09:27.6862235Z  2025-09-07T06:09:27.6863354Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:27.6864789Z  repo = gh.get_repo(repo) 2025-09-07T06:09:27.6865712Z  return repo.get_issue(number=issue_num) 2025-09-07T06:09:27.6866590Z  2025-09-07T06:09:27.6867192Z  2025-09-07T06:09:27.6867826Z def get_potential_pr_author( 2025-09-07T06:09:27.6868969Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:27.6870121Z ) -> str: 2025-09-07T06:09:27.6871071Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:27.6872642Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:27.6874001Z  # embedded in the tag name: ciflow// 2025-09-07T06:09:27.6875024Z  2025-09-07T06:09:27.6875664Z  gh = get_gh_client(github_token) 2025-09-07T06:09:27.6876492Z  2025-09-07T06:09:27.6877295Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:27.6878419Z  split_tag = ref_name.split("/") 2025-09-07T06:09:27.6879517Z  if ( 2025-09-07T06:09:27.6880194Z  len(split_tag) == 3 2025-09-07T06:09:27.6881090Z  and split_tag[0] == "ciflow" 2025-09-07T06:09:27.6882213Z  and split_tag[2].isnumeric() 2025-09-07T06:09:27.6883072Z  ): 2025-09-07T06:09:27.6883754Z  pr_number = split_tag[2] 2025-09-07T06:09:27.6884622Z  try: 2025-09-07T06:09:27.6885436Z  repository = gh.get_repo(repo) 2025-09-07T06:09:27.6886528Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:27.6887564Z  except Exception as e: 2025-09-07T06:09:27.6888502Z  raise Exception( # noqa: TRY002 2025-09-07T06:09:27.6889678Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:27.6890770Z  ) from e 2025-09-07T06:09:27.6891770Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:27.6893220Z  # In all other cases, return the original input username 2025-09-07T06:09:27.6894204Z  return username 2025-09-07T06:09:27.6894896Z  2025-09-07T06:09:27.6895437Z  2025-09-07T06:09:27.6896121Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:27.6896990Z  """ 2025-09-07T06:09:27.6898064Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:27.6899339Z  """ 2025-09-07T06:09:27.6900266Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:27.6901364Z  2025-09-07T06:09:27.6901896Z  2025-09-07T06:09:27.6902671Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:27.6903515Z  try: 2025-09-07T06:09:27.6904201Z  data = yaml.safe_load(yaml_text) 2025-09-07T06:09:27.6905034Z  return data 2025-09-07T06:09:27.6905755Z  except yaml.YAMLError: 2025-09-07T06:09:27.6906558Z  log.exception("Error loading YAML") 2025-09-07T06:09:27.6907388Z  raise 2025-09-07T06:09:27.6908024Z  2025-09-07T06:09:27.6908558Z  2025-09-07T06:09:27.6909563Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:27.6910768Z  """ 2025-09-07T06:09:27.6912130Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:27.6913400Z  2025-09-07T06:09:27.6914284Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:27.6915559Z  and the text below is the list of opted in users. 2025-09-07T06:09:27.6916469Z  2025-09-07T06:09:27.6917415Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:27.6918563Z  """ 2025-09-07T06:09:27.6919339Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:27.6920363Z  if len(rollout_state_parts) >= 2: 2025-09-07T06:09:27.6921410Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:27.6922618Z  else: 2025-09-07T06:09:27.6923385Z  return "", rollout_state 2025-09-07T06:09:27.6924180Z  2025-09-07T06:09:27.6924728Z  2025-09-07T06:09:27.6925408Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:27.6926307Z  """ 2025-09-07T06:09:27.6927256Z  Dictionary of users with a list of features they have opted into 2025-09-07T06:09:27.6928353Z  """ 2025-09-07T06:09:27.6928938Z  2025-09-07T06:09:27.6929502Z  2025-09-07T06:09:27.6930676Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:27.6931793Z  """ 2025-09-07T06:09:27.6933202Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T06:09:27.6934660Z  2025-09-07T06:09:27.6936029Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:27.6937761Z  - Example line: "@User1,lf,split_build" 2025-09-07T06:09:27.6938949Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:27.6940010Z  2025-09-07T06:09:27.6940582Z  2025-09-07T06:09:27.6941163Z  """ 2025-09-07T06:09:27.6941824Z  optins = UserOptins() 2025-09-07T06:09:27.6942890Z  for user in user_optin_text.split("\n"): 2025-09-07T06:09:27.6943843Z  user = user.strip("\r\n\t -") 2025-09-07T06:09:27.6944850Z  if not user or not user.startswith("@"): 2025-09-07T06:09:27.6945804Z  # Not a valid user. Skip 2025-09-07T06:09:27.6946643Z  continue 2025-09-07T06:09:27.6947344Z  2025-09-07T06:09:27.6947941Z  if user: 2025-09-07T06:09:27.6948769Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:27.6949931Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:27.6951004Z  2025-09-07T06:09:27.6951617Z  return optins 2025-09-07T06:09:27.6952535Z  2025-09-07T06:09:27.6953082Z  2025-09-07T06:09:27.6953895Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:27.6954914Z  """ 2025-09-07T06:09:27.6955605Z  Check if the experiment name is valid. 2025-09-07T06:09:27.6956479Z  A valid name: 2025-09-07T06:09:27.6957607Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:27.6959175Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:27.6960354Z  - Cannot contain spaces 2025-09-07T06:09:27.6961142Z  """ 2025-09-07T06:09:27.6961712Z  2025-09-07T06:09:27.6962740Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:27.6964182Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:27.6965407Z  2025-09-07T06:09:27.6965983Z  if valid: 2025-09-07T06:09:27.6966640Z  return True 2025-09-07T06:09:27.6967317Z  2025-09-07T06:09:27.6967868Z  log.error( 2025-09-07T06:09:27.6970300Z  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-09-07T06:09:27.6973014Z  ) 2025-09-07T06:09:27.6973609Z  return False 2025-09-07T06:09:27.6974290Z  2025-09-07T06:09:27.6974832Z  2025-09-07T06:09:27.6975684Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:27.6976751Z  """ 2025-09-07T06:09:27.6977813Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:27.6979036Z  """ 2025-09-07T06:09:27.6979643Z  try: 2025-09-07T06:09:27.6980284Z  if settings_text: 2025-09-07T06:09:27.6981595Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:27.6983138Z  # for easy reading 2025-09-07T06:09:27.6984555Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:27.6986354Z  # the backtick character in shell commands. 2025-09-07T06:09:27.6987395Z  backtick = chr(96) # backtick character 2025-09-07T06:09:27.6988577Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:27.6989745Z  settings = load_yaml(settings_text) 2025-09-07T06:09:27.6990597Z  2025-09-07T06:09:27.6991630Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:27.6993135Z  experiments = {} 2025-09-07T06:09:27.6993924Z  2025-09-07T06:09:27.6994861Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:27.6996204Z  if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:27.6998074Z  # 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-09-07T06:09:27.6999905Z  continue 2025-09-07T06:09:27.7000689Z  2025-09-07T06:09:27.7001301Z  valid_settings = {} 2025-09-07T06:09:27.7002368Z  for setting in exp_settings: 2025-09-07T06:09:27.7003381Z  if setting not in Experiment._fields: 2025-09-07T06:09:27.7004369Z  log.warning( 2025-09-07T06:09:27.7005604Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:27.7006827Z  ) 2025-09-07T06:09:27.7007616Z  else: 2025-09-07T06:09:27.7008542Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:27.7009509Z  2025-09-07T06:09:27.7010327Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:27.7011450Z  return Settings(experiments) 2025-09-07T06:09:27.7012481Z  2025-09-07T06:09:27.7013073Z  except Exception: 2025-09-07T06:09:27.7013951Z  log.exception("Failed to parse settings") 2025-09-07T06:09:27.7014884Z  2025-09-07T06:09:27.7015461Z  return Settings() 2025-09-07T06:09:27.7016169Z  2025-09-07T06:09:27.7016709Z  2025-09-07T06:09:27.7017702Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:27.7018736Z  """ 2025-09-07T06:09:27.7019498Z  Parse settings, if any, from the rollout state. 2025-09-07T06:09:27.7020424Z  2025-09-07T06:09:27.7021361Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:27.7022943Z  and the text below is the list of opted in users. 2025-09-07T06:09:27.7023955Z  2025-09-07T06:09:27.7024990Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:27.7026251Z  """ 2025-09-07T06:09:27.7027219Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:27.7028578Z  return parse_settings_from_text(settings_text) 2025-09-07T06:09:27.7029537Z  2025-09-07T06:09:27.7030096Z  2025-09-07T06:09:27.7030848Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:27.7031830Z  """ 2025-09-07T06:09:27.7032747Z  Parse users from the rollout state. 2025-09-07T06:09:27.7033599Z  2025-09-07T06:09:27.7034144Z  """ 2025-09-07T06:09:27.7035094Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:27.7036412Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:27.7037563Z  2025-09-07T06:09:27.7038114Z  2025-09-07T06:09:27.7039157Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:27.7040467Z  """ 2025-09-07T06:09:27.7041193Z  Check if a user is opted into an experiment 2025-09-07T06:09:27.7042277Z  """ 2025-09-07T06:09:27.7043127Z  return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:27.7044139Z  2025-09-07T06:09:27.7044701Z  2025-09-07T06:09:27.7045749Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:27.7047074Z  """ 2025-09-07T06:09:27.7047882Z  Check if a user explicitly opted out of an experiment 2025-09-07T06:09:27.7048860Z  """ 2025-09-07T06:09:27.7049751Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:27.7050994Z  experiment_optout = "-" + experiment_name 2025-09-07T06:09:27.7052813Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:27.7053865Z  return False 2025-09-07T06:09:27.7054612Z  2025-09-07T06:09:27.7055379Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:27.7056390Z  log.warning( 2025-09-07T06:09:27.7057820Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:27.7059301Z  ) 2025-09-07T06:09:27.7059907Z  2025-09-07T06:09:27.7060497Z  return True 2025-09-07T06:09:27.7061206Z  2025-09-07T06:09:27.7061758Z  2025-09-07T06:09:27.7062536Z def get_runner_prefix( 2025-09-07T06:09:27.7063334Z  rollout_state: str, 2025-09-07T06:09:27.7064210Z  workflow_requestors: Iterable[str], 2025-09-07T06:09:27.7065128Z  branch: str, 2025-09-07T06:09:27.7066024Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:27.7067198Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:27.7068220Z  is_canary: bool = False, 2025-09-07T06:09:27.7069049Z ) -> str: 2025-09-07T06:09:27.7069793Z  settings = parse_settings(rollout_state) 2025-09-07T06:09:27.7070790Z  user_optins = parse_users(rollout_state) 2025-09-07T06:09:27.7071705Z  2025-09-07T06:09:27.7072745Z  fleet_prefix = "" 2025-09-07T06:09:27.7073526Z  prefixes = [] 2025-09-07T06:09:27.7074633Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:27.7076299Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:27.7077525Z  log.info( 2025-09-07T06:09:27.7078735Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:27.7080045Z  ) 2025-09-07T06:09:27.7080709Z  continue 2025-09-07T06:09:27.7081406Z  2025-09-07T06:09:27.7082211Z  if opt_out_experiments: 2025-09-07T06:09:27.7083229Z  if experiment_name in opt_out_experiments: 2025-09-07T06:09:27.7084329Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:27.7085314Z  log.info( 2025-09-07T06:09:27.7087197Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:27.7088844Z  ) 2025-09-07T06:09:27.7089582Z  continue 2025-09-07T06:09:27.7090347Z  2025-09-07T06:09:27.7091245Z  if eligible_experiments: 2025-09-07T06:09:27.7092395Z  if experiment_name not in eligible_experiments: 2025-09-07T06:09:27.7093538Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:27.7094490Z  log.info( 2025-09-07T06:09:27.7095846Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:27.7097307Z  ) 2025-09-07T06:09:27.7098018Z  continue 2025-09-07T06:09:27.7098884Z  elif not experiment_settings.default: 2025-09-07T06:09:27.7099775Z  log.info( 2025-09-07T06:09:27.7100984Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:27.7102402Z  ) 2025-09-07T06:09:27.7103072Z  continue 2025-09-07T06:09:27.7103810Z  2025-09-07T06:09:27.7104618Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:27.7105656Z  opted_out_users = [ 2025-09-07T06:09:27.7106443Z  requestor 2025-09-07T06:09:27.7107299Z  for requestor in workflow_requestors 2025-09-07T06:09:27.7108466Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:27.7109510Z  ] 2025-09-07T06:09:27.7110123Z  2025-09-07T06:09:27.7110738Z  if opted_out_users: 2025-09-07T06:09:27.7111571Z  log.info( 2025-09-07T06:09:27.7112846Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:27.7114011Z  ) 2025-09-07T06:09:27.7114718Z  continue 2025-09-07T06:09:27.7115418Z  2025-09-07T06:09:27.7116205Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:27.7117234Z  opted_in_users = [ 2025-09-07T06:09:27.7118053Z  requestor 2025-09-07T06:09:27.7118890Z  for requestor in workflow_requestors 2025-09-07T06:09:27.7120029Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:27.7121082Z  ] 2025-09-07T06:09:27.7121726Z  2025-09-07T06:09:27.7122512Z  enabled = False 2025-09-07T06:09:27.7123293Z  if opted_in_users: 2025-09-07T06:09:27.7124342Z  log.info( 2025-09-07T06:09:27.7125464Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:27.7126641Z  ) 2025-09-07T06:09:27.7127315Z  enabled = True 2025-09-07T06:09:27.7128065Z  2025-09-07T06:09:27.7128774Z  elif experiment_settings.rollout_perc: 2025-09-07T06:09:27.7130214Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:27.7131808Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:27.7133117Z  log.info( 2025-09-07T06:09:27.7134615Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:27.7136210Z  ) 2025-09-07T06:09:27.7136960Z  enabled = True 2025-09-07T06:09:27.7137747Z  2025-09-07T06:09:27.7138319Z  if enabled: 2025-09-07T06:09:27.7139114Z  label = experiment_name 2025-09-07T06:09:27.7140100Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:27.7141490Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:27.7143413Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:27.7144720Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:27.7145841Z  if is_canary: 2025-09-07T06:09:27.7146739Z  label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:27.7147678Z  fleet_prefix = label 2025-09-07T06:09:27.7148509Z  else: 2025-09-07T06:09:27.7149292Z  prefixes.append(label) 2025-09-07T06:09:27.7150176Z  2025-09-07T06:09:27.7150783Z  if len(prefixes) > 1: 2025-09-07T06:09:27.7151567Z  log.error( 2025-09-07T06:09:27.7153537Z  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-09-07T06:09:27.7155460Z  ) 2025-09-07T06:09:27.7156143Z  prefixes = prefixes[:1] 2025-09-07T06:09:27.7156964Z  2025-09-07T06:09:27.7157590Z  # Fleet always comes first 2025-09-07T06:09:27.7158412Z  if fleet_prefix: 2025-09-07T06:09:27.7159199Z  prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:27.7160056Z  2025-09-07T06:09:27.7160843Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:27.7161783Z  2025-09-07T06:09:27.7162518Z  2025-09-07T06:09:27.7163654Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:27.7164999Z  """ 2025-09-07T06:09:27.7166012Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:27.7167196Z  2025-09-07T06:09:27.7168195Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:27.7169391Z  """ 2025-09-07T06:09:27.7170070Z  gh = get_gh_client(github_token) 2025-09-07T06:09:27.7171037Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:27.7172407Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:27.7173420Z  2025-09-07T06:09:27.7173967Z  2025-09-07T06:09:27.7175010Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:27.7176505Z  for _ in range(num_retries): 2025-09-07T06:09:27.7177326Z  try: 2025-09-07T06:09:27.7178096Z  req = Request(url=url, headers=headers) 2025-09-07T06:09:27.7179248Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:27.7180344Z  return json.loads(content) 2025-09-07T06:09:27.7181223Z  except Exception as e: 2025-09-07T06:09:27.7182429Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:27.7183359Z  2025-09-07T06:09:27.7184313Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:27.7185545Z  return {} 2025-09-07T06:09:27.7186227Z  2025-09-07T06:09:27.7186786Z  2025-09-07T06:09:27.7187332Z @cache 2025-09-07T06:09:27.7188405Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:27.7189730Z  """ 2025-09-07T06:09:27.7190422Z  Dynamically get PR information 2025-09-07T06:09:27.7191230Z  """ 2025-09-07T06:09:27.7192289Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:27.7193398Z  headers = { 2025-09-07T06:09:27.7194212Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:27.7195459Z  "Authorization": f"token {github_token}", 2025-09-07T06:09:27.7196383Z  } 2025-09-07T06:09:27.7197136Z  json_response: dict[str, Any] = download_json( 2025-09-07T06:09:27.7198157Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:27.7199067Z  headers=headers, 2025-09-07T06:09:27.7199866Z  ) 2025-09-07T06:09:27.7200464Z  2025-09-07T06:09:27.7201059Z  if not json_response: 2025-09-07T06:09:27.7202229Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:27.7203334Z  return {} 2025-09-07T06:09:27.7204054Z  2025-09-07T06:09:27.7204654Z  return json_response 2025-09-07T06:09:27.7205392Z  2025-09-07T06:09:27.7205955Z  2025-09-07T06:09:27.7206979Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:27.7208219Z  """ 2025-09-07T06:09:27.7209133Z  Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:27.7210245Z  """ 2025-09-07T06:09:27.7211108Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:27.7212308Z  return { 2025-09-07T06:09:27.7213335Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:27.7214539Z  } 2025-09-07T06:09:27.7215147Z  2025-09-07T06:09:27.7215704Z  2025-09-07T06:09:27.7216290Z def main() -> None: 2025-09-07T06:09:27.7217041Z  args = parse_args() 2025-09-07T06:09:27.7217806Z  2025-09-07T06:09:27.7218544Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:27.7219434Z  2025-09-07T06:09:27.7220050Z  # Check if the PR is opt-out 2025-09-07T06:09:27.7220892Z  if args.pr_number: 2025-09-07T06:09:27.7222307Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:27.7223640Z  if OPT_OUT_LABEL in labels: 2025-09-07T06:09:27.7224483Z  log.info( 2025-09-07T06:09:27.7225732Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:27.7227018Z  ) 2025-09-07T06:09:27.7227984Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:27.7229160Z  sys.exit() 2025-09-07T06:09:27.7230103Z  2025-09-07T06:09:27.7230683Z  try: 2025-09-07T06:09:27.7231429Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:27.7232869Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:27.7233977Z  ) 2025-09-07T06:09:27.7234594Z  2025-09-07T06:09:27.7235251Z  username = get_potential_pr_author( 2025-09-07T06:09:27.7236190Z  args.github_token, 2025-09-07T06:09:27.7237057Z  args.github_repo, 2025-09-07T06:09:27.7237880Z  args.github_actor, 2025-09-07T06:09:27.7238731Z  args.github_ref_type, 2025-09-07T06:09:27.7239606Z  args.github_branch, 2025-09-07T06:09:27.7240589Z  ) 2025-09-07T06:09:27.7241207Z  2025-09-07T06:09:27.7242186Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:27.7243214Z  2025-09-07T06:09:27.7243934Z  runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:27.7244867Z  rollout_state, 2025-09-07T06:09:27.7245723Z  (args.github_issue_owner, username), 2025-09-07T06:09:27.7246668Z  args.github_branch, 2025-09-07T06:09:27.7247848Z  args.eligible_experiments, 2025-09-07T06:09:27.7248782Z  args.opt_out_experiments, 2025-09-07T06:09:27.7249626Z  is_canary, 2025-09-07T06:09:27.7250382Z  ) 2025-09-07T06:09:27.7251032Z  2025-09-07T06:09:27.7251633Z  except Exception as e: 2025-09-07T06:09:27.7252615Z  log.error( 2025-09-07T06:09:27.7253794Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:27.7255110Z  ) 2025-09-07T06:09:27.7255753Z  2025-09-07T06:09:27.7256622Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:27.7257749Z  2025-09-07T06:09:27.7258312Z  2025-09-07T06:09:27.7258896Z if __name__ == "__main__": 2025-09-07T06:09:27.7259642Z  main() 2025-09-07T06:09:27.7260275Z  2025-09-07T06:09:27.7260856Z EOF 2025-09-07T06:09:27.7261483Z  2025-09-07T06:09:27.7262264Z cat runner_determinator.py 2025-09-07T06:09:28.0473535Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:28.0474330Z env: 2025-09-07T06:09:28.0474927Z GITHUB_TOKEN: *** 2025-09-07T06:09:28.0475339Z ISSUE_NUMBER: 5132 2025-09-07T06:09:28.0475771Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:28.0476262Z ISSUE_OWNER: 2025-09-07T06:09:28.0476643Z CHECK_EXPERIMENTS: 2025-09-07T06:09:28.0477046Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:28.0477461Z PR_NUMBER: 2025-09-07T06:09:28.0477821Z ##[endgroup] 2025-09-07T06:09:28.0682735Z # flake8: noqa: G004 2025-09-07T06:09:28.0683054Z 2025-09-07T06:09:28.0683464Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:28.0684371Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:28.0685158Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:28.0685594Z 2025-09-07T06:09:28.0685749Z """ 2025-09-07T06:09:28.0686291Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:28.0687129Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:28.0687996Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:28.0688773Z of which runners should be used to run which job. 2025-09-07T06:09:28.0689149Z 2025-09-07T06:09:28.0689521Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:28.0690545Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:28.0691419Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:28.0692683Z list, defined. 2025-09-07T06:09:28.0692931Z 2025-09-07T06:09:28.0693284Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:28.0694144Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:28.0694970Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:28.0695397Z 2025-09-07T06:09:28.0695751Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:28.0696567Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:28.0697263Z experiments which the user could be opted in to. 2025-09-07T06:09:28.0697646Z 2025-09-07T06:09:28.0697830Z The user list has the following rules: 2025-09-07T06:09:28.0698160Z 2025-09-07T06:09:28.0698455Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:28.0699260Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:28.0700031Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:28.0700408Z 2025-09-07T06:09:28.0700567Z Example config: 2025-09-07T06:09:28.0701024Z # A list of experiments that can be opted into. 2025-09-07T06:09:28.0701842Z # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:28.0702633Z # Expected syntax is: 2025-09-07T06:09:28.0703729Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:28.0705300Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:28.0706154Z 2025-09-07T06:09:28.0706333Z experiments: 2025-09-07T06:09:28.0706970Z lf: 2025-09-07T06:09:28.0707344Z rollout_percent: 25 2025-09-07T06:09:28.0707775Z all_branches: false 2025-09-07T06:09:28.0708205Z default: true 2025-09-07T06:09:28.0708591Z --- 2025-09-07T06:09:28.0708782Z 2025-09-07T06:09:28.0708934Z # Opt-ins: 2025-09-07T06:09:28.0709483Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:28.0710293Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:28.0711028Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:28.0711647Z # Experiments should be from the above list. 2025-09-07T06:09:28.0712185Z 2025-09-07T06:09:28.0712429Z @User1,-lf,split_build 2025-09-07T06:09:28.0712855Z @User2,lf 2025-09-07T06:09:28.0713215Z @User3,split_build 2025-09-07T06:09:28.0713594Z """ 2025-09-07T06:09:28.0713775Z 2025-09-07T06:09:28.0713933Z import json 2025-09-07T06:09:28.0714329Z import logging 2025-09-07T06:09:28.0714724Z import os 2025-09-07T06:09:28.0715108Z import random 2025-09-07T06:09:28.0715506Z import re 2025-09-07T06:09:28.0715883Z import sys 2025-09-07T06:09:28.0716305Z from argparse import ArgumentParser 2025-09-07T06:09:28.0716841Z from collections.abc import Iterable 2025-09-07T06:09:28.0717331Z from functools import cache 2025-09-07T06:09:28.0717770Z from logging import LogRecord 2025-09-07T06:09:28.0718229Z from typing import Any, NamedTuple 2025-09-07T06:09:28.0718725Z from urllib.request import Request, urlopen 2025-09-07T06:09:28.0719088Z 2025-09-07T06:09:28.0719240Z import yaml 2025-09-07T06:09:28.0719598Z from github import Auth, Github 2025-09-07T06:09:28.0720054Z from github.Issue import Issue 2025-09-07T06:09:28.0720337Z 2025-09-07T06:09:28.0720343Z 2025-09-07T06:09:28.0720546Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:28.0721186Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:28.0722181Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:28.0722766Z 2025-09-07T06:09:28.0722977Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:28.0723680Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:28.0724164Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:28.0724681Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:28.0725011Z 2025-09-07T06:09:28.0725196Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:28.0725512Z 2025-09-07T06:09:28.0725680Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:28.0726246Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:28.0726558Z 2025-09-07T06:09:28.0726564Z 2025-09-07T06:09:28.0726762Z class Experiment(NamedTuple): 2025-09-07T06:09:28.0727353Z rollout_perc: float = ( 2025-09-07T06:09:28.0727950Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:28.0728601Z ) 2025-09-07T06:09:28.0729028Z all_branches: bool = ( 2025-09-07T06:09:28.0729661Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:28.0730463Z ) 2025-09-07T06:09:28.0730822Z default: bool = ( 2025-09-07T06:09:28.0731359Z True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:28.0731961Z ) 2025-09-07T06:09:28.0732328Z 2025-09-07T06:09:28.0732496Z # Add more fields as needed 2025-09-07T06:09:28.0732776Z 2025-09-07T06:09:28.0732782Z 2025-09-07T06:09:28.0732965Z class Settings(NamedTuple): 2025-09-07T06:09:28.0733379Z """ 2025-09-07T06:09:28.0733945Z Settings for the experiments that can be opted into. 2025-09-07T06:09:28.0734483Z """ 2025-09-07T06:09:28.0734663Z 2025-09-07T06:09:28.0734860Z experiments: dict[str, Experiment] = {} 2025-09-07T06:09:28.0735201Z 2025-09-07T06:09:28.0735207Z 2025-09-07T06:09:28.0735398Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:28.0735981Z """Color codes the log messages based on the log level""" 2025-09-07T06:09:28.0736389Z 2025-09-07T06:09:28.0736545Z COLORS = { 2025-09-07T06:09:28.0736911Z "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:28.0737391Z "ERROR": "\033[31m", # Red 2025-09-07T06:09:28.0737848Z "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:28.0738314Z "INFO": "\033[0m", # Reset 2025-09-07T06:09:28.0738763Z "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:28.0739197Z } 2025-09-07T06:09:28.0739378Z 2025-09-07T06:09:28.0739576Z def format(self, record: LogRecord) -> str: 2025-09-07T06:09:28.0740284Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:28.0741023Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:28.0741558Z return super().format(record) 2025-09-07T06:09:28.0741876Z 2025-09-07T06:09:28.0741883Z 2025-09-07T06:09:28.0742258Z handler = logging.StreamHandler() 2025-09-07T06:09:28.0742939Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:28.0743478Z 2025-09-07T06:09:28.0743701Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:28.0744250Z log.addHandler(handler) 2025-09-07T06:09:28.0744675Z log.setLevel(logging.INFO) 2025-09-07T06:09:28.0744941Z 2025-09-07T06:09:28.0744948Z 2025-09-07T06:09:28.0745184Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:28.0745709Z """ 2025-09-07T06:09:28.0746183Z Defines outputs of the github action that invokes this script 2025-09-07T06:09:28.0746774Z """ 2025-09-07T06:09:28.0747453Z if not GITHUB_OUTPUT: 2025-09-07T06:09:28.0749014Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:28.0750825Z log.warning( 2025-09-07T06:09:28.0752342Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T06:09:28.0753861Z ) 2025-09-07T06:09:28.0767244Z print(f"::set-output name={key}::{value}") 2025-09-07T06:09:28.0768135Z return 2025-09-07T06:09:28.0768493Z 2025-09-07T06:09:28.0769002Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:28.0769916Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:28.0770834Z f.write(f"{key}={value}\n") 2025-09-07T06:09:28.0771334Z 2025-09-07T06:09:28.0771344Z 2025-09-07T06:09:28.0771828Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:28.0773033Z return frozenset( 2025-09-07T06:09:28.0773979Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:28.0775093Z ) 2025-09-07T06:09:28.0775426Z 2025-09-07T06:09:28.0775436Z 2025-09-07T06:09:28.0775721Z def parse_args() -> Any: 2025-09-07T06:09:28.0776594Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:28.0777710Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:28.0828156Z parser.add_argument( 2025-09-07T06:09:28.0828676Z "--github-issue-repo", 2025-09-07T06:09:28.0829160Z type=str, 2025-09-07T06:09:28.0829587Z required=False, 2025-09-07T06:09:28.0830024Z default="pytorch/test-infra", 2025-09-07T06:09:28.0830537Z help="GitHub repo to get the issue", 2025-09-07T06:09:28.0831039Z ) 2025-09-07T06:09:28.0831420Z parser.add_argument( 2025-09-07T06:09:28.0831846Z "--github-repo", 2025-09-07T06:09:28.0832446Z type=str, 2025-09-07T06:09:28.0832834Z required=True, 2025-09-07T06:09:28.0833461Z help="GitHub repo where CI is running", 2025-09-07T06:09:28.0833994Z ) 2025-09-07T06:09:28.0834350Z parser.add_argument( 2025-09-07T06:09:28.0834925Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:28.0835581Z ) 2025-09-07T06:09:28.0835930Z parser.add_argument( 2025-09-07T06:09:28.0836522Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:28.0837162Z ) 2025-09-07T06:09:28.0837514Z parser.add_argument( 2025-09-07T06:09:28.0838141Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:28.0838866Z ) 2025-09-07T06:09:28.0839209Z parser.add_argument( 2025-09-07T06:09:28.0839821Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:28.0840513Z ) 2025-09-07T06:09:28.0840849Z parser.add_argument( 2025-09-07T06:09:28.0841276Z "--github-ref-type", 2025-09-07T06:09:28.0841708Z type=str, 2025-09-07T06:09:28.0842292Z required=True, 2025-09-07T06:09:28.0842758Z help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:28.0843279Z ) 2025-09-07T06:09:28.0843709Z parser.add_argument( 2025-09-07T06:09:28.0844184Z "--eligible-experiments", 2025-09-07T06:09:28.0844674Z type=_str_comma_separated_to_set, 2025-09-07T06:09:28.0845162Z required=False, 2025-09-07T06:09:28.0845560Z default="", 2025-09-07T06:09:28.0846368Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:28.0847260Z ) 2025-09-07T06:09:28.0847604Z parser.add_argument( 2025-09-07T06:09:28.0848039Z "--opt-out-experiments", 2025-09-07T06:09:28.0848504Z type=_str_comma_separated_to_set, 2025-09-07T06:09:28.0848999Z required=False, 2025-09-07T06:09:28.0849395Z default="", 2025-09-07T06:09:28.0849769Z help=( 2025-09-07T06:09:28.0850399Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:28.0851478Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:28.0852480Z ), 2025-09-07T06:09:28.0852815Z ) 2025-09-07T06:09:28.0853174Z parser.add_argument( 2025-09-07T06:09:28.0853583Z "--pr-number", 2025-09-07T06:09:28.0853980Z type=str, 2025-09-07T06:09:28.0854351Z required=False, 2025-09-07T06:09:28.0854741Z default="", 2025-09-07T06:09:28.0855328Z help="the optional PR number where this is run", 2025-09-07T06:09:28.0855889Z ) 2025-09-07T06:09:28.0856084Z 2025-09-07T06:09:28.0856264Z return parser.parse_args() 2025-09-07T06:09:28.0856554Z 2025-09-07T06:09:28.0856560Z 2025-09-07T06:09:28.0856941Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.0857664Z auth = Auth.Token(github_token) 2025-09-07T06:09:28.0858134Z return Github(auth=auth) 2025-09-07T06:09:28.0858420Z 2025-09-07T06:09:28.0858426Z 2025-09-07T06:09:28.0858856Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.0859627Z repo = gh.get_repo(repo) 2025-09-07T06:09:28.0860106Z return repo.get_issue(number=issue_num) 2025-09-07T06:09:28.0860469Z 2025-09-07T06:09:28.0860474Z 2025-09-07T06:09:28.0860669Z def get_potential_pr_author( 2025-09-07T06:09:28.0861297Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:28.0861947Z ) -> str: 2025-09-07T06:09:28.0862658Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:28.0863415Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:28.0864112Z # embedded in the tag name: ciflow// 2025-09-07T06:09:28.0864506Z 2025-09-07T06:09:28.0864813Z gh = get_gh_client(github_token) 2025-09-07T06:09:28.0865137Z 2025-09-07T06:09:28.0865406Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:28.0866024Z split_tag = ref_name.split("/") 2025-09-07T06:09:28.0866504Z if ( 2025-09-07T06:09:28.0866855Z len(split_tag) == 3 2025-09-07T06:09:28.0867302Z and split_tag[0] == "ciflow" 2025-09-07T06:09:28.0867803Z and split_tag[2].isnumeric() 2025-09-07T06:09:28.0868268Z ): 2025-09-07T06:09:28.0868623Z pr_number = split_tag[2] 2025-09-07T06:09:28.0869108Z try: 2025-09-07T06:09:28.0869553Z repository = gh.get_repo(repo) 2025-09-07T06:09:28.0870117Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:28.0870682Z except Exception as e: 2025-09-07T06:09:28.0871163Z raise Exception( # noqa: TRY002 2025-09-07T06:09:28.0871799Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:28.0872631Z ) from e 2025-09-07T06:09:28.0873123Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:28.0873775Z # In all other cases, return the original input username 2025-09-07T06:09:28.0874326Z return username 2025-09-07T06:09:28.0874555Z 2025-09-07T06:09:28.0874561Z 2025-09-07T06:09:28.0874768Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:28.0875260Z """ 2025-09-07T06:09:28.0875858Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:28.0876639Z """ 2025-09-07T06:09:28.0877169Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:28.0877667Z 2025-09-07T06:09:28.0877674Z 2025-09-07T06:09:28.0877858Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:28.0878318Z try: 2025-09-07T06:09:28.0878680Z data = yaml.safe_load(yaml_text) 2025-09-07T06:09:28.0879162Z return data 2025-09-07T06:09:28.0879550Z except yaml.YAMLError: 2025-09-07T06:09:28.0880030Z log.exception("Error loading YAML") 2025-09-07T06:09:28.0880519Z raise 2025-09-07T06:09:28.0880720Z 2025-09-07T06:09:28.0880727Z 2025-09-07T06:09:28.0881123Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:28.0881821Z """ 2025-09-07T06:09:28.0882526Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:28.0883112Z 2025-09-07T06:09:28.0883562Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.0884279Z and the text below is the list of opted in users. 2025-09-07T06:09:28.0884660Z 2025-09-07T06:09:28.0885014Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:28.0885666Z """ 2025-09-07T06:09:28.0886081Z rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:28.0886642Z if len(rollout_state_parts) >= 2: 2025-09-07T06:09:28.0887204Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:28.0887748Z else: 2025-09-07T06:09:28.0888108Z return "", rollout_state 2025-09-07T06:09:28.0888413Z 2025-09-07T06:09:28.0888419Z 2025-09-07T06:09:28.0888625Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:28.0889102Z """ 2025-09-07T06:09:28.0889587Z Dictionary of users with a list of features they have opted into 2025-09-07T06:09:28.0890191Z """ 2025-09-07T06:09:28.0890367Z 2025-09-07T06:09:28.0890374Z 2025-09-07T06:09:28.0890697Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:28.0891303Z """ 2025-09-07T06:09:28.0891972Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T06:09:28.0892748Z 2025-09-07T06:09:28.0893336Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:28.0894407Z - Example line: "@User1,lf,split_build" 2025-09-07T06:09:28.0895078Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:28.0895534Z 2025-09-07T06:09:28.0895541Z 2025-09-07T06:09:28.0895688Z """ 2025-09-07T06:09:28.0896040Z optins = UserOptins() 2025-09-07T06:09:28.0896485Z for user in user_optin_text.split("\n"): 2025-09-07T06:09:28.0897006Z user = user.strip("\r\n\t -") 2025-09-07T06:09:28.0897511Z if not user or not user.startswith("@"): 2025-09-07T06:09:28.0898028Z # Not a valid user. Skip 2025-09-07T06:09:28.0898474Z continue 2025-09-07T06:09:28.0898695Z 2025-09-07T06:09:28.0898841Z if user: 2025-09-07T06:09:28.0899250Z usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:28.0899901Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:28.0900375Z 2025-09-07T06:09:28.0900541Z return optins 2025-09-07T06:09:28.0900758Z 2025-09-07T06:09:28.0900765Z 2025-09-07T06:09:28.0901032Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:28.0901585Z """ 2025-09-07T06:09:28.0901961Z Check if the experiment name is valid. 2025-09-07T06:09:28.0902881Z A valid name: 2025-09-07T06:09:28.0903473Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:28.0904355Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:28.0905033Z - Cannot contain spaces 2025-09-07T06:09:28.0905459Z """ 2025-09-07T06:09:28.0905649Z 2025-09-07T06:09:28.0905893Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:28.0906546Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:28.0906966Z 2025-09-07T06:09:28.0907114Z if valid: 2025-09-07T06:09:28.0907465Z return True 2025-09-07T06:09:28.0907687Z 2025-09-07T06:09:28.0907834Z log.error( 2025-09-07T06:09:28.0909227Z 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-09-07T06:09:28.0910775Z ) 2025-09-07T06:09:28.0911128Z return False 2025-09-07T06:09:28.0911351Z 2025-09-07T06:09:28.0911357Z 2025-09-07T06:09:28.0911632Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:28.0912352Z """ 2025-09-07T06:09:28.0913080Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:28.0913771Z """ 2025-09-07T06:09:28.0914097Z try: 2025-09-07T06:09:28.0914439Z if settings_text: 2025-09-07T06:09:28.0915123Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:28.0915864Z # for easy reading 2025-09-07T06:09:28.0916599Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:28.0917430Z # the backtick character in shell commands. 2025-09-07T06:09:28.0917987Z backtick = chr(96) # backtick character 2025-09-07T06:09:28.0918613Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:28.0919227Z settings = load_yaml(settings_text) 2025-09-07T06:09:28.0919570Z 2025-09-07T06:09:28.0919953Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:28.0920660Z experiments = {} 2025-09-07T06:09:28.0920929Z 2025-09-07T06:09:28.0921283Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:28.0922095Z if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:28.0923149Z # 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-09-07T06:09:28.0924257Z continue 2025-09-07T06:09:28.0924517Z 2025-09-07T06:09:28.0924694Z valid_settings = {} 2025-09-07T06:09:28.0925173Z for setting in exp_settings: 2025-09-07T06:09:28.0925708Z if setting not in Experiment._fields: 2025-09-07T06:09:28.0926222Z log.warning( 2025-09-07T06:09:28.0926878Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:28.0927556Z ) 2025-09-07T06:09:28.0927943Z else: 2025-09-07T06:09:28.0928417Z valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:28.0928810Z 2025-09-07T06:09:28.0929061Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:28.0929652Z return Settings(experiments) 2025-09-07T06:09:28.0929980Z 2025-09-07T06:09:28.0930141Z except Exception: 2025-09-07T06:09:28.0930581Z log.exception("Failed to parse settings") 2025-09-07T06:09:28.0930952Z 2025-09-07T06:09:28.0931117Z return Settings() 2025-09-07T06:09:28.0931355Z 2025-09-07T06:09:28.0931361Z 2025-09-07T06:09:28.0931591Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:28.0932227Z """ 2025-09-07T06:09:28.0932620Z Parse settings, if any, from the rollout state. 2025-09-07T06:09:28.0932999Z 2025-09-07T06:09:28.0933325Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.0934034Z and the text below is the list of opted in users. 2025-09-07T06:09:28.0934417Z 2025-09-07T06:09:28.0934793Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:28.0935478Z """ 2025-09-07T06:09:28.0935980Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.0936691Z return parse_settings_from_text(settings_text) 2025-09-07T06:09:28.0937064Z 2025-09-07T06:09:28.0937070Z 2025-09-07T06:09:28.0937292Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:28.0937820Z """ 2025-09-07T06:09:28.0938177Z Parse users from the rollout state. 2025-09-07T06:09:28.0938512Z 2025-09-07T06:09:28.0938659Z """ 2025-09-07T06:09:28.0939152Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.0939829Z return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:28.0940197Z 2025-09-07T06:09:28.0940209Z 2025-09-07T06:09:28.0940711Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.0941418Z """ 2025-09-07T06:09:28.0941802Z Check if a user is opted into an experiment 2025-09-07T06:09:28.0942410Z """ 2025-09-07T06:09:28.0942812Z return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:28.0943196Z 2025-09-07T06:09:28.0943208Z 2025-09-07T06:09:28.0943600Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.0944331Z """ 2025-09-07T06:09:28.0944795Z Check if a user explicitly opted out of an experiment 2025-09-07T06:09:28.0945364Z """ 2025-09-07T06:09:28.0945848Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:28.0946477Z experiment_optout = "-" + experiment_name 2025-09-07T06:09:28.0947066Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:28.0947617Z return False 2025-09-07T06:09:28.0947841Z 2025-09-07T06:09:28.0948092Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:28.0948647Z log.warning( 2025-09-07T06:09:28.0949383Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:28.0950210Z ) 2025-09-07T06:09:28.0950394Z 2025-09-07T06:09:28.0950543Z return True 2025-09-07T06:09:28.0950943Z 2025-09-07T06:09:28.0950950Z 2025-09-07T06:09:28.0951115Z def get_runner_prefix( 2025-09-07T06:09:28.0951519Z rollout_state: str, 2025-09-07T06:09:28.0951936Z workflow_requestors: Iterable[str], 2025-09-07T06:09:28.0952516Z branch: str, 2025-09-07T06:09:28.0952953Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.0953654Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.0954534Z is_canary: bool = False, 2025-09-07T06:09:28.0955092Z ) -> str: 2025-09-07T06:09:28.0955466Z settings = parse_settings(rollout_state) 2025-09-07T06:09:28.0956007Z user_optins = parse_users(rollout_state) 2025-09-07T06:09:28.0956353Z 2025-09-07T06:09:28.0956514Z fleet_prefix = "" 2025-09-07T06:09:28.0956895Z prefixes = [] 2025-09-07T06:09:28.0957469Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:28.0958366Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:28.0959032Z log.info( 2025-09-07T06:09:28.0959642Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:28.0960364Z ) 2025-09-07T06:09:28.0960754Z continue 2025-09-07T06:09:28.0960997Z 2025-09-07T06:09:28.0961191Z if opt_out_experiments: 2025-09-07T06:09:28.0961721Z if experiment_name in opt_out_experiments: 2025-09-07T06:09:28.0962440Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:28.0962985Z log.info( 2025-09-07T06:09:28.0963854Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:28.0964780Z ) 2025-09-07T06:09:28.0965136Z continue 2025-09-07T06:09:28.0965379Z 2025-09-07T06:09:28.0965550Z if eligible_experiments: 2025-09-07T06:09:28.0966053Z if experiment_name not in eligible_experiments: 2025-09-07T06:09:28.0966640Z exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:28.0967161Z log.info( 2025-09-07T06:09:28.0967885Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:28.0968666Z ) 2025-09-07T06:09:28.0969018Z continue 2025-09-07T06:09:28.0969452Z elif not experiment_settings.default: 2025-09-07T06:09:28.0969943Z log.info( 2025-09-07T06:09:28.0970686Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:28.0971406Z ) 2025-09-07T06:09:28.0971790Z continue 2025-09-07T06:09:28.0972113Z 2025-09-07T06:09:28.0972370Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:28.0972931Z opted_out_users = [ 2025-09-07T06:09:28.0973348Z requestor 2025-09-07T06:09:28.0973763Z for requestor in workflow_requestors 2025-09-07T06:09:28.0974386Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.0974973Z ] 2025-09-07T06:09:28.0975161Z 2025-09-07T06:09:28.0975326Z if opted_out_users: 2025-09-07T06:09:28.0975740Z log.info( 2025-09-07T06:09:28.0976316Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:28.0977001Z ) 2025-09-07T06:09:28.0977379Z continue 2025-09-07T06:09:28.0977624Z 2025-09-07T06:09:28.0977890Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:28.0978450Z opted_in_users = [ 2025-09-07T06:09:28.0978854Z requestor 2025-09-07T06:09:28.0979266Z for requestor in workflow_requestors 2025-09-07T06:09:28.0979870Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.0980437Z ] 2025-09-07T06:09:28.0980739Z 2025-09-07T06:09:28.0980892Z enabled = False 2025-09-07T06:09:28.0981284Z if opted_in_users: 2025-09-07T06:09:28.0981679Z log.info( 2025-09-07T06:09:28.0982328Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:28.0982958Z ) 2025-09-07T06:09:28.0983307Z enabled = True 2025-09-07T06:09:28.0983559Z 2025-09-07T06:09:28.0983763Z elif experiment_settings.rollout_perc: 2025-09-07T06:09:28.0984534Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:28.0985404Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:28.0986003Z log.info( 2025-09-07T06:09:28.0986804Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:28.0987671Z ) 2025-09-07T06:09:28.0988043Z enabled = True 2025-09-07T06:09:28.0988311Z 2025-09-07T06:09:28.0988468Z if enabled: 2025-09-07T06:09:28.0988858Z label = experiment_name 2025-09-07T06:09:28.0989367Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:28.0990131Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:28.0990958Z # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:28.0991659Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:28.0992370Z if is_canary: 2025-09-07T06:09:28.0992823Z label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:28.0993327Z fleet_prefix = label 2025-09-07T06:09:28.0993778Z else: 2025-09-07T06:09:28.0994166Z prefixes.append(label) 2025-09-07T06:09:28.0994493Z 2025-09-07T06:09:28.0994657Z if len(prefixes) > 1: 2025-09-07T06:09:28.0995135Z log.error( 2025-09-07T06:09:28.0996118Z 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-09-07T06:09:28.0997175Z ) 2025-09-07T06:09:28.0997526Z prefixes = prefixes[:1] 2025-09-07T06:09:28.0997810Z 2025-09-07T06:09:28.0997983Z # Fleet always comes first 2025-09-07T06:09:28.0998408Z if fleet_prefix: 2025-09-07T06:09:28.0998833Z prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:28.0999187Z 2025-09-07T06:09:28.0999555Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:28.0999975Z 2025-09-07T06:09:28.0999981Z 2025-09-07T06:09:28.1000409Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:28.1001239Z """ 2025-09-07T06:09:28.1001778Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:28.1002407Z 2025-09-07T06:09:28.1002765Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:28.1003409Z """ 2025-09-07T06:09:28.1003767Z gh = get_gh_client(github_token) 2025-09-07T06:09:28.1004260Z issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:28.1004852Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:28.1005262Z 2025-09-07T06:09:28.1005268Z 2025-09-07T06:09:28.1005636Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:28.1006333Z for _ in range(num_retries): 2025-09-07T06:09:28.1006773Z try: 2025-09-07T06:09:28.1007155Z req = Request(url=url, headers=headers) 2025-09-07T06:09:28.1007791Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:28.1008375Z return json.loads(content) 2025-09-07T06:09:28.1008852Z except Exception as e: 2025-09-07T06:09:28.1009329Z log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:28.1009837Z 2025-09-07T06:09:28.1010184Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:28.1010852Z return {} 2025-09-07T06:09:28.1011052Z 2025-09-07T06:09:28.1011058Z 2025-09-07T06:09:28.1011219Z @cache 2025-09-07T06:09:28.1011810Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:28.1012649Z """ 2025-09-07T06:09:28.1013006Z Dynamically get PR information 2025-09-07T06:09:28.1013442Z """ 2025-09-07T06:09:28.1013899Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:28.1014472Z headers = { 2025-09-07T06:09:28.1014887Z "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:28.1015445Z "Authorization": f"token {github_token}", 2025-09-07T06:09:28.1015934Z } 2025-09-07T06:09:28.1016319Z json_response: dict[str, Any] = download_json( 2025-09-07T06:09:28.1016874Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:28.1017374Z headers=headers, 2025-09-07T06:09:28.1017764Z ) 2025-09-07T06:09:28.1017947Z 2025-09-07T06:09:28.1018113Z if not json_response: 2025-09-07T06:09:28.1018623Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:28.1019186Z return {} 2025-09-07T06:09:28.1019398Z 2025-09-07T06:09:28.1019566Z return json_response 2025-09-07T06:09:28.1019815Z 2025-09-07T06:09:28.1019821Z 2025-09-07T06:09:28.1020185Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:28.1020868Z """ 2025-09-07T06:09:28.1021341Z Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:28.1021943Z """ 2025-09-07T06:09:28.1022469Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:28.1023034Z return { 2025-09-07T06:09:28.1023573Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:28.1024267Z } 2025-09-07T06:09:28.1024468Z 2025-09-07T06:09:28.1024474Z 2025-09-07T06:09:28.1024658Z def main() -> None: 2025-09-07T06:09:28.1025074Z args = parse_args() 2025-09-07T06:09:28.1025317Z 2025-09-07T06:09:28.1025515Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:28.1025864Z 2025-09-07T06:09:28.1026036Z # Check if the PR is opt-out 2025-09-07T06:09:28.1026488Z if args.pr_number: 2025-09-07T06:09:28.1027079Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:28.1027890Z if OPT_OUT_LABEL in labels: 2025-09-07T06:09:28.1028358Z log.info( 2025-09-07T06:09:28.1028995Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:28.1029706Z ) 2025-09-07T06:09:28.1030198Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.1030815Z sys.exit() 2025-09-07T06:09:28.1031058Z 2025-09-07T06:09:28.1031208Z try: 2025-09-07T06:09:28.1031603Z rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:28.1032346Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:28.1032932Z ) 2025-09-07T06:09:28.1033114Z 2025-09-07T06:09:28.1033300Z username = get_potential_pr_author( 2025-09-07T06:09:28.1033787Z args.github_token, 2025-09-07T06:09:28.1034245Z args.github_repo, 2025-09-07T06:09:28.1034713Z args.github_actor, 2025-09-07T06:09:28.1035189Z args.github_ref_type, 2025-09-07T06:09:28.1035637Z args.github_branch, 2025-09-07T06:09:28.1036052Z ) 2025-09-07T06:09:28.1036239Z 2025-09-07T06:09:28.1036496Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:28.1036912Z 2025-09-07T06:09:28.1037117Z runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:28.1037627Z rollout_state, 2025-09-07T06:09:28.1038188Z (args.github_issue_owner, username), 2025-09-07T06:09:28.1038703Z args.github_branch, 2025-09-07T06:09:28.1039144Z args.eligible_experiments, 2025-09-07T06:09:28.1039635Z args.opt_out_experiments, 2025-09-07T06:09:28.1040135Z is_canary, 2025-09-07T06:09:28.1040512Z ) 2025-09-07T06:09:28.1040696Z 2025-09-07T06:09:28.1040864Z except Exception as e: 2025-09-07T06:09:28.1041268Z log.error( 2025-09-07T06:09:28.1041877Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:28.1042731Z ) 2025-09-07T06:09:28.1042940Z 2025-09-07T06:09:28.1043257Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.1043739Z 2025-09-07T06:09:28.1043744Z 2025-09-07T06:09:28.1043931Z if __name__ == "__main__": 2025-09-07T06:09:28.1044324Z main() 2025-09-07T06:09:28.1044510Z 2025-09-07T06:09:28.1126653Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:28.1127499Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:28.1157182Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:28.1157632Z env: 2025-09-07T06:09:28.1158195Z GITHUB_TOKEN: *** 2025-09-07T06:09:28.1158588Z ISSUE_NUMBER: 5132 2025-09-07T06:09:28.1159031Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:28.1159496Z ISSUE_OWNER: 2025-09-07T06:09:28.1159863Z CHECK_EXPERIMENTS: 2025-09-07T06:09:28.1160261Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:28.1160658Z PR_NUMBER: 2025-09-07T06:09:28.1161017Z ##[endgroup] 2025-09-07T06:09:30.7923754Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T06:09:31.8563728Z Collecting urllib3==1.26.18 2025-09-07T06:09:31.8902459Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T06:09:31.9096300Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.5 MB/s eta 0:00:00 2025-09-07T06:09:31.9310668Z Collecting PyGithub==2.3.0 2025-09-07T06:09:31.9359017Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T06:09:31.9761374Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T06:09:31.9795659Z 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-09-07T06:09:31.9839036Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-09-07T06:09:31.9851406Z 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-09-07T06:09:31.9870223Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-09-07T06:09:32.0120385Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T06:09:32.0154909Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T06:09:32.0380104Z 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-09-07T06:09:32.1502983Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:32.1536135Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T06:09:32.2770706Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T06:09:32.2805166Z 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-09-07T06:09:32.2986365Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:32.3018207Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T06:09:32.3239380Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T06:09:32.3296566Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 34.7 MB/s eta 0:00:00 2025-09-07T06:09:32.3331669Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T06:09:32.3405730Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 61.8 MB/s eta 0:00:00 2025-09-07T06:09:32.3438182Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-09-07T06:09:32.3517198Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 138.6 MB/s eta 0:00:00 2025-09-07T06:09:32.3549282Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T06:09:32.3599815Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T06:09:32.3659042Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 111.5 MB/s eta 0:00:00 2025-09-07T06:09:32.3694404Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-09-07T06:09:32.3733039Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 33.9 MB/s eta 0:00:00 2025-09-07T06:09:32.3765091Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T06:09:32.3804667Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 44.6 MB/s eta 0:00:00 2025-09-07T06:09:32.7254993Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T06:09:33.2453328Z 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-09-07T06:09:33.3228290Z ##[group]Run curr_branch="main" 2025-09-07T06:09:33.3228601Z curr_branch="main" 2025-09-07T06:09:33.3228819Z curr_ref_type="branch" 2025-09-07T06:09:33.3229069Z echo "Current branch is '$curr_branch'" 2025-09-07T06:09:33.3229317Z  2025-09-07T06:09:33.3229515Z python3 runner_determinator.py \ 2025-09-07T06:09:33.3229807Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T06:09:33.3230079Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T06:09:33.3230328Z  --github-branch "$curr_branch" \ 2025-09-07T06:09:33.3230615Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T06:09:33.3230893Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T06:09:33.3231163Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T06:09:33.3231432Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T06:09:33.3231740Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T06:09:33.3232281Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T06:09:33.3232572Z  --pr-number "${PR_NUMBER}" 2025-09-07T06:09:33.3262858Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:33.3263086Z env: 2025-09-07T06:09:33.3263637Z GITHUB_TOKEN: *** 2025-09-07T06:09:33.3263818Z ISSUE_NUMBER: 5132 2025-09-07T06:09:33.3264019Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:33.3264248Z ISSUE_OWNER: 2025-09-07T06:09:33.3264421Z CHECK_EXPERIMENTS: 2025-09-07T06:09:33.3264606Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:33.3264795Z PR_NUMBER: 2025-09-07T06:09:33.3264964Z ##[endgroup] 2025-09-07T06:09:33.3311469Z Current branch is 'main' 2025-09-07T06:09:34.8786900Z INFO : Based on rollout percentage of 60%, enabling experiment lf. 2025-09-07T06:09:34.8787786Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T06:09:34.8788501Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T06:09:34.8789179Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T06:09:34.8789709Z INFO : Setting output: label-type='lf.' 2025-09-07T06:09:34.9108638Z Evaluate and set job outputs 2025-09-07T06:09:34.9115306Z Set output 'label-type' 2025-09-07T06:09:34.9117230Z Cleaning up orphan processes