2025-09-07T06:09:28.8013518Z Current runner version: '2.328.0' 2025-09-07T06:09:28.8037790Z ##[group]Runner Image Provisioner 2025-09-07T06:09:28.8038556Z Hosted Compute Agent 2025-09-07T06:09:28.8039093Z Version: 20250829.383 2025-09-07T06:09:28.8040063Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T06:09:28.8040862Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T06:09:28.8041421Z ##[endgroup] 2025-09-07T06:09:28.8042035Z ##[group]Operating System 2025-09-07T06:09:28.8042600Z Ubuntu 2025-09-07T06:09:28.8043046Z 24.04.3 2025-09-07T06:09:28.8043573Z LTS 2025-09-07T06:09:28.8044034Z ##[endgroup] 2025-09-07T06:09:28.8044521Z ##[group]Runner Image 2025-09-07T06:09:28.8045045Z Image: ubuntu-24.04 2025-09-07T06:09:28.8045573Z Version: 20250831.1.0 2025-09-07T06:09:28.8046524Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T06:09:28.8048155Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T06:09:28.8049127Z ##[endgroup] 2025-09-07T06:09:28.8050297Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T06:09:28.8052396Z Contents: read 2025-09-07T06:09:28.8052876Z Metadata: read 2025-09-07T06:09:28.8053455Z ##[endgroup] 2025-09-07T06:09:28.8055495Z Secret source: Actions 2025-09-07T06:09:28.8056468Z Prepare workflow directory 2025-09-07T06:09:28.8567580Z Prepare all required actions 2025-09-07T06:09:28.8623381Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T06:09:28.8628044Z ##[group] Inputs 2025-09-07T06:09:28.8628713Z check_experiments: 2025-09-07T06:09:28.8629257Z opt_out_experiments: 2025-09-07T06:09:28.8630023Z triggering_actor: pytorchmergebot 2025-09-07T06:09:28.8630579Z issue_owner: 2025-09-07T06:09:28.8631151Z curr_branch: main 2025-09-07T06:09:28.8631626Z curr_ref_type: branch 2025-09-07T06:09:28.8632258Z issue_number: 5132 2025-09-07T06:09:28.8632889Z ##[endgroup] 2025-09-07T06:09:28.8633534Z Complete job name: before-test / get-label-type / runner-determinator 2025-09-07T06:09:29.5945079Z ##[group]Run cat < runner_determinator.py 2025-09-07T06:09:29.5947546Z cat < runner_determinator.py 2025-09-07T06:09:29.5948274Z # flake8: noqa: G004 2025-09-07T06:09:29.5948877Z  2025-09-07T06:09:29.5949718Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:29.5951323Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:29.5952466Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:29.5953257Z  2025-09-07T06:09:29.5953711Z """ 2025-09-07T06:09:29.5954512Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:29.5955577Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:29.5956795Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:29.5957913Z of which runners should be used to run which job. 2025-09-07T06:09:29.5958650Z  2025-09-07T06:09:29.5959392Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:29.5960724Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:29.5961815Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:29.5962648Z list, defined. 2025-09-07T06:09:29.5963280Z  2025-09-07T06:09:29.5964014Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:29.5965088Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:29.5966168Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:29.5966905Z  2025-09-07T06:09:29.5967922Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:29.5969064Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:29.5970221Z experiments which the user could be opted in to. 2025-09-07T06:09:29.5970965Z  2025-09-07T06:09:29.5971508Z The user list has the following rules: 2025-09-07T06:09:29.5972184Z  2025-09-07T06:09:29.5972875Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:29.5973982Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:29.5974960Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:29.5975621Z  2025-09-07T06:09:29.5976149Z Example config: 2025-09-07T06:09:29.5976788Z  # A list of experiments that can be opted into. 2025-09-07T06:09:29.5977675Z  # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:29.5978515Z  # Expected syntax is: 2025-09-07T06:09:29.5979344Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:29.5980636Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:29.5981508Z  2025-09-07T06:09:29.5982075Z  experiments: 2025-09-07T06:09:29.5982628Z  lf: 2025-09-07T06:09:29.5983501Z  rollout_percent: 25 2025-09-07T06:09:29.6062965Z  all_branches: false 2025-09-07T06:09:29.6063534Z  default: true 2025-09-07T06:09:29.6064018Z  --- 2025-09-07T06:09:29.6064468Z  2025-09-07T06:09:29.6064868Z  # Opt-ins: 2025-09-07T06:09:29.6065566Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:29.6066813Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:29.6067691Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:29.6068425Z  # Experiments should be from the above list. 2025-09-07T06:09:29.6069037Z  2025-09-07T06:09:29.6069445Z  @User1,-lf,split_build 2025-09-07T06:09:29.6070113Z  @User2,lf 2025-09-07T06:09:29.6070603Z  @User3,split_build 2025-09-07T06:09:29.6071112Z """ 2025-09-07T06:09:29.6071510Z  2025-09-07T06:09:29.6071896Z import json 2025-09-07T06:09:29.6072350Z import logging 2025-09-07T06:09:29.6072804Z import os 2025-09-07T06:09:29.6073243Z import random 2025-09-07T06:09:29.6073693Z import re 2025-09-07T06:09:29.6074115Z import sys 2025-09-07T06:09:29.6074593Z from argparse import ArgumentParser 2025-09-07T06:09:29.6075283Z from collections.abc import Iterable 2025-09-07T06:09:29.6075883Z from functools import cache 2025-09-07T06:09:29.6076427Z from logging import LogRecord 2025-09-07T06:09:29.6077005Z from typing import Any, NamedTuple 2025-09-07T06:09:29.6077627Z from urllib.request import Request, urlopen 2025-09-07T06:09:29.6078210Z  2025-09-07T06:09:29.6078602Z import yaml 2025-09-07T06:09:29.6079076Z from github import Auth, Github 2025-09-07T06:09:29.6079651Z from github.Issue import Issue 2025-09-07T06:09:29.6080333Z  2025-09-07T06:09:29.6080725Z  2025-09-07T06:09:29.6081201Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:29.6081976Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:29.6082939Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:29.6083692Z  2025-09-07T06:09:29.6084363Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:29.6084999Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:29.6085612Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:29.6086266Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:29.6086850Z  2025-09-07T06:09:29.6087291Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:29.6087837Z  2025-09-07T06:09:29.6088253Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:29.6088791Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:29.6089296Z  2025-09-07T06:09:29.6089677Z  2025-09-07T06:09:29.6090214Z class Experiment(NamedTuple): 2025-09-07T06:09:29.6090781Z  rollout_perc: float = ( 2025-09-07T06:09:29.6091543Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:29.6092280Z  ) 2025-09-07T06:09:29.6092705Z  all_branches: bool = ( 2025-09-07T06:09:29.6093447Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:29.6094172Z  ) 2025-09-07T06:09:29.6094600Z  default: bool = ( 2025-09-07T06:09:29.6095271Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:29.6095972Z  ) 2025-09-07T06:09:29.6096360Z  2025-09-07T06:09:29.6096766Z  # Add more fields as needed 2025-09-07T06:09:29.6097289Z  2025-09-07T06:09:29.6097663Z  2025-09-07T06:09:29.6098073Z class Settings(NamedTuple): 2025-09-07T06:09:29.6098585Z  """ 2025-09-07T06:09:29.6099137Z  Settings for the experiments that can be opted into. 2025-09-07T06:09:29.6099859Z  """ 2025-09-07T06:09:29.6100265Z  2025-09-07T06:09:29.6100729Z  experiments: dict[str, Experiment] = {} 2025-09-07T06:09:29.6101310Z  2025-09-07T06:09:29.6101816Z  2025-09-07T06:09:29.6102285Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:29.6103005Z  """Color codes the log messages based on the log level""" 2025-09-07T06:09:29.6103635Z  2025-09-07T06:09:29.6104028Z  COLORS = { 2025-09-07T06:09:29.6104506Z  "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:29.6105079Z  "ERROR": "\033[31m", # Red 2025-09-07T06:09:29.6105638Z  "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:29.6106193Z  "INFO": "\033[0m", # Reset 2025-09-07T06:09:29.6106745Z  "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:29.6107269Z  } 2025-09-07T06:09:29.6107673Z  2025-09-07T06:09:29.6108138Z  def format(self, record: LogRecord) -> str: 2025-09-07T06:09:29.6108962Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:29.6109923Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:29.6110567Z  return super().format(record) 2025-09-07T06:09:29.6111106Z  2025-09-07T06:09:29.6111473Z  2025-09-07T06:09:29.6111901Z handler = logging.StreamHandler() 2025-09-07T06:09:29.6112704Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:29.6113485Z  2025-09-07T06:09:29.6113989Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:29.6114652Z log.addHandler(handler) 2025-09-07T06:09:29.6115177Z log.setLevel(logging.INFO) 2025-09-07T06:09:29.6115668Z  2025-09-07T06:09:29.6116045Z  2025-09-07T06:09:29.6116549Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:29.6117166Z  """ 2025-09-07T06:09:29.6117744Z  Defines outputs of the github action that invokes this script 2025-09-07T06:09:29.6118572Z  """ 2025-09-07T06:09:29.6118999Z  if not GITHUB_OUTPUT: 2025-09-07T06:09:29.6120315Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:29.6121518Z  log.warning( 2025-09-07T06:09:29.6122471Z  "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:29.6123460Z  ) 2025-09-07T06:09:29.6123966Z  print(f"::set-output name={key}::{value}") 2025-09-07T06:09:29.6124556Z  return 2025-09-07T06:09:29.6124997Z  2025-09-07T06:09:29.6125426Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:29.6126077Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:29.6126710Z  f.write(f"{key}={value}\n") 2025-09-07T06:09:29.6127256Z  2025-09-07T06:09:29.6127639Z  2025-09-07T06:09:29.6128199Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:29.6128920Z  return frozenset( 2025-09-07T06:09:29.6129630Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:29.6130710Z  ) 2025-09-07T06:09:29.6131123Z  2025-09-07T06:09:29.6131507Z  2025-09-07T06:09:29.6131919Z def parse_args() -> Any: 2025-09-07T06:09:29.6132592Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:29.6133534Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:29.6134393Z  parser.add_argument( 2025-09-07T06:09:29.6134938Z  "--github-issue-repo", 2025-09-07T06:09:29.6135477Z  type=str, 2025-09-07T06:09:29.6135977Z  required=False, 2025-09-07T06:09:29.6136657Z  default="pytorch/test-infra", 2025-09-07T06:09:29.6137299Z  help="GitHub repo to get the issue", 2025-09-07T06:09:29.6137863Z  ) 2025-09-07T06:09:29.6138300Z  parser.add_argument( 2025-09-07T06:09:29.6138828Z  "--github-repo", 2025-09-07T06:09:29.6139330Z  type=str, 2025-09-07T06:09:29.6139935Z  required=True, 2025-09-07T06:09:29.6140505Z  help="GitHub repo where CI is running", 2025-09-07T06:09:29.6141088Z  ) 2025-09-07T06:09:29.6141521Z  parser.add_argument( 2025-09-07T06:09:29.6142245Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:29.6142952Z  ) 2025-09-07T06:09:29.6143395Z  parser.add_argument( 2025-09-07T06:09:29.6144125Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:29.6144867Z  ) 2025-09-07T06:09:29.6145302Z  parser.add_argument( 2025-09-07T06:09:29.6146024Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:29.6146772Z  ) 2025-09-07T06:09:29.6147202Z  parser.add_argument( 2025-09-07T06:09:29.6147959Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:29.6148729Z  ) 2025-09-07T06:09:29.6149189Z  parser.add_argument( 2025-09-07T06:09:29.6149730Z  "--github-ref-type", 2025-09-07T06:09:29.6150358Z  type=str, 2025-09-07T06:09:29.6150837Z  required=True, 2025-09-07T06:09:29.6151430Z  help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:29.6152020Z  ) 2025-09-07T06:09:29.6152449Z  parser.add_argument( 2025-09-07T06:09:29.6153132Z  "--eligible-experiments", 2025-09-07T06:09:29.6153726Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:29.6154304Z  required=False, 2025-09-07T06:09:29.6154803Z  default="", 2025-09-07T06:09:29.6155753Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:29.6156730Z  ) 2025-09-07T06:09:29.6157158Z  parser.add_argument( 2025-09-07T06:09:29.6157691Z  "--opt-out-experiments", 2025-09-07T06:09:29.6158282Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:29.6158848Z  required=False, 2025-09-07T06:09:29.6159345Z  default="", 2025-09-07T06:09:29.6159960Z  help=( 2025-09-07T06:09:29.6160723Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:29.6161938Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:29.6162821Z  ), 2025-09-07T06:09:29.6163239Z  ) 2025-09-07T06:09:29.6163658Z  parser.add_argument( 2025-09-07T06:09:29.6164168Z  "--pr-number", 2025-09-07T06:09:29.6164661Z  type=str, 2025-09-07T06:09:29.6165135Z  required=False, 2025-09-07T06:09:29.6165638Z  default="", 2025-09-07T06:09:29.6166198Z  help="the optional PR number where this is run", 2025-09-07T06:09:29.6166814Z  ) 2025-09-07T06:09:29.6167218Z  2025-09-07T06:09:29.6167630Z  return parser.parse_args() 2025-09-07T06:09:29.6168160Z  2025-09-07T06:09:29.6168534Z  2025-09-07T06:09:29.6169197Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:29.6170264Z  auth = Auth.Token(github_token) 2025-09-07T06:09:29.6170911Z  return Github(auth=auth) 2025-09-07T06:09:29.6171423Z  2025-09-07T06:09:29.6171804Z  2025-09-07T06:09:29.6172517Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:29.6173384Z  repo = gh.get_repo(repo) 2025-09-07T06:09:29.6173986Z  return repo.get_issue(number=issue_num) 2025-09-07T06:09:29.6174554Z  2025-09-07T06:09:29.6174937Z  2025-09-07T06:09:29.6175341Z def get_potential_pr_author( 2025-09-07T06:09:29.6176079Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:29.6176818Z ) -> str: 2025-09-07T06:09:29.6177406Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:29.6178298Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:29.6179126Z  # embedded in the tag name: ciflow// 2025-09-07T06:09:29.6179739Z  2025-09-07T06:09:29.6180269Z  gh = get_gh_client(github_token) 2025-09-07T06:09:29.6180836Z  2025-09-07T06:09:29.6181359Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:29.6182051Z  split_tag = ref_name.split("/") 2025-09-07T06:09:29.6182611Z  if ( 2025-09-07T06:09:29.6183065Z  len(split_tag) == 3 2025-09-07T06:09:29.6183640Z  and split_tag[0] == "ciflow" 2025-09-07T06:09:29.6184230Z  and split_tag[2].isnumeric() 2025-09-07T06:09:29.6184776Z  ): 2025-09-07T06:09:29.6185249Z  pr_number = split_tag[2] 2025-09-07T06:09:29.6185793Z  try: 2025-09-07T06:09:29.6186309Z  repository = gh.get_repo(repo) 2025-09-07T06:09:29.6187132Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:29.6187813Z  except Exception as e: 2025-09-07T06:09:29.6188410Z  raise Exception( # noqa: TRY002 2025-09-07T06:09:29.6189156Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:29.6189960Z  ) from e 2025-09-07T06:09:29.6190593Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:29.6191372Z  # In all other cases, return the original input username 2025-09-07T06:09:29.6192014Z  return username 2025-09-07T06:09:29.6192497Z  2025-09-07T06:09:29.6192873Z  2025-09-07T06:09:29.6193350Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:29.6193935Z  """ 2025-09-07T06:09:29.6194662Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:29.6195507Z  """ 2025-09-07T06:09:29.6196120Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:29.6196839Z  2025-09-07T06:09:29.6197208Z  2025-09-07T06:09:29.6197641Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:29.6198188Z  try: 2025-09-07T06:09:29.6198643Z  data = yaml.safe_load(yaml_text) 2025-09-07T06:09:29.6199211Z  return data 2025-09-07T06:09:29.6199708Z  except yaml.YAMLError: 2025-09-07T06:09:29.6200423Z  log.exception("Error loading YAML") 2025-09-07T06:09:29.6200986Z  raise 2025-09-07T06:09:29.6201421Z  2025-09-07T06:09:29.6201790Z  2025-09-07T06:09:29.6202462Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:29.6203257Z  """ 2025-09-07T06:09:29.6204082Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:29.6204886Z  2025-09-07T06:09:29.6205463Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:29.6206309Z  and the text below is the list of opted in users. 2025-09-07T06:09:29.6206925Z  2025-09-07T06:09:29.6207553Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:29.6208312Z  """ 2025-09-07T06:09:29.6208821Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:29.6209478Z  if len(rollout_state_parts) >= 2: 2025-09-07T06:09:29.6210791Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:29.6211441Z  else: 2025-09-07T06:09:29.6211895Z  return "", rollout_state 2025-09-07T06:09:29.6212679Z  2025-09-07T06:09:29.6213059Z  2025-09-07T06:09:29.6213501Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:29.6214068Z  """ 2025-09-07T06:09:29.6214664Z  Dictionary of users with a list of features they have opted into 2025-09-07T06:09:29.6215367Z  """ 2025-09-07T06:09:29.6215763Z  2025-09-07T06:09:29.6216135Z  2025-09-07T06:09:29.6216713Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:29.6217421Z  """ 2025-09-07T06:09:29.6218211Z  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:29.6219091Z  2025-09-07T06:09:29.6220066Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:29.6221128Z  - Example line: "@User1,lf,split_build" 2025-09-07T06:09:29.6222031Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:29.6222713Z  2025-09-07T06:09:29.6223071Z  2025-09-07T06:09:29.6223444Z  """ 2025-09-07T06:09:29.6223864Z  optins = UserOptins() 2025-09-07T06:09:29.6224432Z  for user in user_optin_text.split("\n"): 2025-09-07T06:09:29.6225040Z  user = user.strip("\r\n\t -") 2025-09-07T06:09:29.6225660Z  if not user or not user.startswith("@"): 2025-09-07T06:09:29.6226266Z  # Not a valid user. Skip 2025-09-07T06:09:29.6226814Z  continue 2025-09-07T06:09:29.6227271Z  2025-09-07T06:09:29.6227650Z  if user: 2025-09-07T06:09:29.6228207Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:29.6228959Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:29.6229648Z  2025-09-07T06:09:29.6230152Z  return optins 2025-09-07T06:09:29.6230615Z  2025-09-07T06:09:29.6230985Z  2025-09-07T06:09:29.6231532Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:29.6232191Z  """ 2025-09-07T06:09:29.6232661Z  Check if the experiment name is valid. 2025-09-07T06:09:29.6233238Z  A valid name: 2025-09-07T06:09:29.6233993Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:29.6235002Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:29.6235764Z  - Cannot contain spaces 2025-09-07T06:09:29.6236288Z  """ 2025-09-07T06:09:29.6236681Z  2025-09-07T06:09:29.6237182Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:29.6237955Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:29.6238723Z  2025-09-07T06:09:29.6239115Z  if valid: 2025-09-07T06:09:29.6239556Z  return True 2025-09-07T06:09:29.6240119Z  2025-09-07T06:09:29.6240501Z  log.error( 2025-09-07T06:09:29.6242041Z  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:29.6243674Z  ) 2025-09-07T06:09:29.6244082Z  return False 2025-09-07T06:09:29.6244530Z  2025-09-07T06:09:29.6244898Z  2025-09-07T06:09:29.6245464Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:29.6246153Z  """ 2025-09-07T06:09:29.6246814Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:29.6247592Z  """ 2025-09-07T06:09:29.6247995Z  try: 2025-09-07T06:09:29.6248424Z  if settings_text: 2025-09-07T06:09:29.6249244Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:29.6250187Z  # for easy reading 2025-09-07T06:09:29.6251067Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:29.6252024Z  # the backtick character in shell commands. 2025-09-07T06:09:29.6252694Z  backtick = chr(96) # backtick character 2025-09-07T06:09:29.6253434Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:29.6254161Z  settings = load_yaml(settings_text) 2025-09-07T06:09:29.6254725Z  2025-09-07T06:09:29.6255368Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:29.6256295Z  experiments = {} 2025-09-07T06:09:29.6256800Z  2025-09-07T06:09:29.6257412Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:29.6258230Z  if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:29.6259408Z  # 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:29.6260616Z  continue 2025-09-07T06:09:29.6261113Z  2025-09-07T06:09:29.6261529Z  valid_settings = {} 2025-09-07T06:09:29.6262114Z  for setting in exp_settings: 2025-09-07T06:09:29.6262745Z  if setting not in Experiment._fields: 2025-09-07T06:09:29.6263358Z  log.warning( 2025-09-07T06:09:29.6264144Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:29.6264900Z  ) 2025-09-07T06:09:29.6265390Z  else: 2025-09-07T06:09:29.6265988Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:29.6266593Z  2025-09-07T06:09:29.6267115Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:29.6267815Z  return Settings(experiments) 2025-09-07T06:09:29.6268352Z  2025-09-07T06:09:29.6268749Z  except Exception: 2025-09-07T06:09:29.6269316Z  log.exception("Failed to parse settings") 2025-09-07T06:09:29.6269998Z  2025-09-07T06:09:29.6270389Z  return Settings() 2025-09-07T06:09:29.6270866Z  2025-09-07T06:09:29.6271237Z  2025-09-07T06:09:29.6271908Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:29.6272546Z  """ 2025-09-07T06:09:29.6273058Z  Parse settings, if any, from the rollout state. 2025-09-07T06:09:29.6273658Z  2025-09-07T06:09:29.6274247Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:29.6275076Z  and the text below is the list of opted in users. 2025-09-07T06:09:29.6275677Z  2025-09-07T06:09:29.6276335Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:29.6277113Z  """ 2025-09-07T06:09:29.6277740Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:29.6278566Z  return parse_settings_from_text(settings_text) 2025-09-07T06:09:29.6279151Z  2025-09-07T06:09:29.6279528Z  2025-09-07T06:09:29.6280132Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:29.6280751Z  """ 2025-09-07T06:09:29.6281208Z  Parse users from the rollout state. 2025-09-07T06:09:29.6281770Z  2025-09-07T06:09:29.6282145Z  """ 2025-09-07T06:09:29.6282743Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:29.6283560Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:29.6284144Z  2025-09-07T06:09:29.6284517Z  2025-09-07T06:09:29.6285189Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:29.6285989Z  """ 2025-09-07T06:09:29.6286480Z  Check if a user is opted into an experiment 2025-09-07T06:09:29.6287057Z  """ 2025-09-07T06:09:29.6287584Z  return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:29.6288204Z  2025-09-07T06:09:29.6288711Z  2025-09-07T06:09:29.6289392Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:29.6290346Z  """ 2025-09-07T06:09:29.6290882Z  Check if a user explicitly opted out of an experiment 2025-09-07T06:09:29.6291513Z  """ 2025-09-07T06:09:29.6292099Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:29.6292857Z  experiment_optout = "-" + experiment_name 2025-09-07T06:09:29.6293576Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:29.6294220Z  return False 2025-09-07T06:09:29.6294688Z  2025-09-07T06:09:29.6295199Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:29.6295846Z  log.warning( 2025-09-07T06:09:29.6296745Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:29.6297674Z  ) 2025-09-07T06:09:29.6298083Z  2025-09-07T06:09:29.6298466Z  return True 2025-09-07T06:09:29.6298917Z  2025-09-07T06:09:29.6299280Z  2025-09-07T06:09:29.6299677Z def get_runner_prefix( 2025-09-07T06:09:29.6300291Z  rollout_state: str, 2025-09-07T06:09:29.6300845Z  workflow_requestors: Iterable[str], 2025-09-07T06:09:29.6301413Z  branch: str, 2025-09-07T06:09:29.6301992Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:29.6302739Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:29.6303368Z  is_canary: bool = False, 2025-09-07T06:09:29.6303882Z ) -> str: 2025-09-07T06:09:29.6304380Z  settings = parse_settings(rollout_state) 2025-09-07T06:09:29.6305038Z  user_optins = parse_users(rollout_state) 2025-09-07T06:09:29.6305612Z  2025-09-07T06:09:29.6306140Z  fleet_prefix = "" 2025-09-07T06:09:29.6306654Z  prefixes = [] 2025-09-07T06:09:29.6307370Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:29.6308387Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:29.6309153Z  log.info( 2025-09-07T06:09:29.6310021Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:29.6310819Z  ) 2025-09-07T06:09:29.6311264Z  continue 2025-09-07T06:09:29.6311721Z  2025-09-07T06:09:29.6312137Z  if opt_out_experiments: 2025-09-07T06:09:29.6312752Z  if experiment_name in opt_out_experiments: 2025-09-07T06:09:29.6313465Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:29.6314115Z  log.info( 2025-09-07T06:09:29.6315132Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:29.6316149Z  ) 2025-09-07T06:09:29.6316622Z  continue 2025-09-07T06:09:29.6317107Z  2025-09-07T06:09:29.6317526Z  if eligible_experiments: 2025-09-07T06:09:29.6318175Z  if experiment_name not in eligible_experiments: 2025-09-07T06:09:29.6318902Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:29.6319503Z  log.info( 2025-09-07T06:09:29.6320459Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:29.6321339Z  ) 2025-09-07T06:09:29.6321932Z  continue 2025-09-07T06:09:29.6322491Z  elif not experiment_settings.default: 2025-09-07T06:09:29.6323070Z  log.info( 2025-09-07T06:09:29.6323809Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:29.6324589Z  ) 2025-09-07T06:09:29.6325023Z  continue 2025-09-07T06:09:29.6325486Z  2025-09-07T06:09:29.6325999Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:29.6326677Z  opted_out_users = [ 2025-09-07T06:09:29.6327203Z  requestor 2025-09-07T06:09:29.6327738Z  for requestor in workflow_requestors 2025-09-07T06:09:29.6328479Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:29.6329153Z  ] 2025-09-07T06:09:29.6329580Z  2025-09-07T06:09:29.6330146Z  if opted_out_users: 2025-09-07T06:09:29.6330692Z  log.info( 2025-09-07T06:09:29.6331402Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:29.6332155Z  ) 2025-09-07T06:09:29.6332606Z  continue 2025-09-07T06:09:29.6333059Z  2025-09-07T06:09:29.6333572Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:29.6334259Z  opted_in_users = [ 2025-09-07T06:09:29.6334783Z  requestor 2025-09-07T06:09:29.6335325Z  for requestor in workflow_requestors 2025-09-07T06:09:29.6336060Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:29.6336730Z  ] 2025-09-07T06:09:29.6337138Z  2025-09-07T06:09:29.6337532Z  enabled = False 2025-09-07T06:09:29.6338050Z  if opted_in_users: 2025-09-07T06:09:29.6338694Z  log.info( 2025-09-07T06:09:29.6339400Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:29.6340231Z  ) 2025-09-07T06:09:29.6340696Z  enabled = True 2025-09-07T06:09:29.6341188Z  2025-09-07T06:09:29.6341638Z  elif experiment_settings.rollout_perc: 2025-09-07T06:09:29.6342528Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:29.6343542Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:29.6344232Z  log.info( 2025-09-07T06:09:29.6345181Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:29.6346146Z  ) 2025-09-07T06:09:29.6346630Z  enabled = True 2025-09-07T06:09:29.6347146Z  2025-09-07T06:09:29.6347533Z  if enabled: 2025-09-07T06:09:29.6348041Z  label = experiment_name 2025-09-07T06:09:29.6348655Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:29.6349542Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:29.6350585Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:29.6351401Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:29.6352113Z  if is_canary: 2025-09-07T06:09:29.6352671Z  label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:29.6353269Z  fleet_prefix = label 2025-09-07T06:09:29.6353814Z  else: 2025-09-07T06:09:29.6354450Z  prefixes.append(label) 2025-09-07T06:09:29.6354999Z  2025-09-07T06:09:29.6355394Z  if len(prefixes) > 1: 2025-09-07T06:09:29.6355912Z  log.error( 2025-09-07T06:09:29.6357023Z  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:29.6358187Z  ) 2025-09-07T06:09:29.6358649Z  prefixes = prefixes[:1] 2025-09-07T06:09:29.6359173Z  2025-09-07T06:09:29.6359584Z  # Fleet always comes first 2025-09-07T06:09:29.6360261Z  if fleet_prefix: 2025-09-07T06:09:29.6360794Z  prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:29.6361345Z  2025-09-07T06:09:29.6361845Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:29.6362452Z  2025-09-07T06:09:29.6362830Z  2025-09-07T06:09:29.6363520Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:29.6364330Z  """ 2025-09-07T06:09:29.6364985Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:29.6365727Z  2025-09-07T06:09:29.6366355Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:29.6367099Z  """ 2025-09-07T06:09:29.6367550Z  gh = get_gh_client(github_token) 2025-09-07T06:09:29.6368168Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:29.6368877Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:29.6369519Z  2025-09-07T06:09:29.6370000Z  2025-09-07T06:09:29.6370664Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:29.6371603Z  for _ in range(num_retries): 2025-09-07T06:09:29.6372148Z  try: 2025-09-07T06:09:29.6372657Z  req = Request(url=url, headers=headers) 2025-09-07T06:09:29.6373376Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:29.6374080Z  return json.loads(content) 2025-09-07T06:09:29.6374652Z  except Exception as e: 2025-09-07T06:09:29.6375276Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:29.6375860Z  2025-09-07T06:09:29.6376484Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:29.6377247Z  return {} 2025-09-07T06:09:29.6377678Z  2025-09-07T06:09:29.6378047Z  2025-09-07T06:09:29.6378415Z @cache 2025-09-07T06:09:29.6379111Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:29.6380122Z  """ 2025-09-07T06:09:29.6380586Z  Dynamically get PR information 2025-09-07T06:09:29.6381119Z  """ 2025-09-07T06:09:29.6381697Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:29.6382383Z  headers = { 2025-09-07T06:09:29.6382921Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:29.6383592Z  "Authorization": f"token {github_token}", 2025-09-07T06:09:29.6384161Z  } 2025-09-07T06:09:29.6384661Z  json_response: dict[str, Any] = download_json( 2025-09-07T06:09:29.6385320Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:29.6385917Z  headers=headers, 2025-09-07T06:09:29.6386404Z  ) 2025-09-07T06:09:29.6386793Z  2025-09-07T06:09:29.6387200Z  if not json_response: 2025-09-07T06:09:29.6387850Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:29.6388665Z  return {} 2025-09-07T06:09:29.6389122Z  2025-09-07T06:09:29.6389532Z  return json_response 2025-09-07T06:09:29.6390130Z  2025-09-07T06:09:29.6390501Z  2025-09-07T06:09:29.6391143Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:29.6391997Z  """ 2025-09-07T06:09:29.6392913Z  Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:29.6393817Z  """ 2025-09-07T06:09:29.6394405Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:29.6395159Z  return { 2025-09-07T06:09:29.6396300Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:29.6397291Z  } 2025-09-07T06:09:29.6397928Z  2025-09-07T06:09:29.6398526Z  2025-09-07T06:09:29.6399088Z def main() -> None: 2025-09-07T06:09:29.6400102Z  args = parse_args() 2025-09-07T06:09:29.6400932Z  2025-09-07T06:09:29.6401428Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:29.6402015Z  2025-09-07T06:09:29.6402433Z  # Check if the PR is opt-out 2025-09-07T06:09:29.6403000Z  if args.pr_number: 2025-09-07T06:09:29.6403745Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:29.6404561Z  if OPT_OUT_LABEL in labels: 2025-09-07T06:09:29.6405119Z  log.info( 2025-09-07T06:09:29.6405897Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:29.6406705Z  ) 2025-09-07T06:09:29.6407359Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:29.6408104Z  sys.exit() 2025-09-07T06:09:29.6408767Z  2025-09-07T06:09:29.6409174Z  try: 2025-09-07T06:09:29.6409681Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:29.6410638Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:29.6411326Z  ) 2025-09-07T06:09:29.6411747Z  2025-09-07T06:09:29.6412190Z  username = get_potential_pr_author( 2025-09-07T06:09:29.6412787Z  args.github_token, 2025-09-07T06:09:29.6413339Z  args.github_repo, 2025-09-07T06:09:29.6413878Z  args.github_actor, 2025-09-07T06:09:29.6414442Z  args.github_ref_type, 2025-09-07T06:09:29.6414993Z  args.github_branch, 2025-09-07T06:09:29.6415513Z  ) 2025-09-07T06:09:29.6415924Z  2025-09-07T06:09:29.6416462Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:29.6417120Z  2025-09-07T06:09:29.6417579Z  runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:29.6418192Z  rollout_state, 2025-09-07T06:09:29.6418755Z  (args.github_issue_owner, username), 2025-09-07T06:09:29.6419591Z  args.github_branch, 2025-09-07T06:09:29.6420439Z  args.eligible_experiments, 2025-09-07T06:09:29.6421286Z  args.opt_out_experiments, 2025-09-07T06:09:29.6421855Z  is_canary, 2025-09-07T06:09:29.6422331Z  ) 2025-09-07T06:09:29.6422746Z  2025-09-07T06:09:29.6423146Z  except Exception as e: 2025-09-07T06:09:29.6423684Z  log.error( 2025-09-07T06:09:29.6424442Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:29.6425424Z  ) 2025-09-07T06:09:29.6425841Z  2025-09-07T06:09:29.6426420Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:29.6427113Z  2025-09-07T06:09:29.6427476Z  2025-09-07T06:09:29.6427865Z if __name__ == "__main__": 2025-09-07T06:09:29.6428360Z  main() 2025-09-07T06:09:29.6428780Z  2025-09-07T06:09:29.6429139Z EOF 2025-09-07T06:09:29.6429532Z  2025-09-07T06:09:29.6430075Z cat runner_determinator.py 2025-09-07T06:09:29.6707931Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:29.6708828Z env: 2025-09-07T06:09:29.6709617Z GITHUB_TOKEN: *** 2025-09-07T06:09:29.6710272Z ISSUE_NUMBER: 5132 2025-09-07T06:09:29.6710763Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:29.6711315Z ISSUE_OWNER: 2025-09-07T06:09:29.6711748Z CHECK_EXPERIMENTS: 2025-09-07T06:09:29.6712208Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:29.6712669Z PR_NUMBER: 2025-09-07T06:09:29.6713105Z ##[endgroup] 2025-09-07T06:09:29.6922072Z # flake8: noqa: G004 2025-09-07T06:09:29.6922443Z 2025-09-07T06:09:29.6922885Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:29.6923846Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:29.6924652Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:29.6925099Z 2025-09-07T06:09:29.6925270Z """ 2025-09-07T06:09:29.6925854Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:29.6926735Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:29.6927636Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:29.6928455Z of which runners should be used to run which job. 2025-09-07T06:09:29.6928858Z 2025-09-07T06:09:29.6929248Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:29.6930689Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:29.6931622Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:29.6932321Z list, defined. 2025-09-07T06:09:29.6932560Z 2025-09-07T06:09:29.6932921Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:29.6933840Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:29.6934668Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:29.6935109Z 2025-09-07T06:09:29.6935486Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:29.6936353Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:29.6937092Z experiments which the user could be opted in to. 2025-09-07T06:09:29.6937497Z 2025-09-07T06:09:29.6937708Z The user list has the following rules: 2025-09-07T06:09:29.6938066Z 2025-09-07T06:09:29.6938396Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:29.6939259Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:29.6940669Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:29.6941082Z 2025-09-07T06:09:29.6941262Z Example config: 2025-09-07T06:09:29.6941718Z # A list of experiments that can be opted into. 2025-09-07T06:09:29.6942400Z # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:29.6943028Z # Expected syntax is: 2025-09-07T06:09:29.6943678Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:29.6944653Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:29.6945266Z 2025-09-07T06:09:29.6945438Z experiments: 2025-09-07T06:09:29.6945840Z lf: 2025-09-07T06:09:29.6946219Z rollout_percent: 25 2025-09-07T06:09:29.6946887Z all_branches: false 2025-09-07T06:09:29.6947345Z default: true 2025-09-07T06:09:29.6947761Z --- 2025-09-07T06:09:29.6947972Z 2025-09-07T06:09:29.6948141Z # Opt-ins: 2025-09-07T06:09:29.6948732Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:29.6949608Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:29.6950686Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:29.6951389Z # Experiments should be from the above list. 2025-09-07T06:09:29.6951771Z 2025-09-07T06:09:29.6951956Z @User1,-lf,split_build 2025-09-07T06:09:29.6952402Z @User2,lf 2025-09-07T06:09:29.6952785Z @User3,split_build 2025-09-07T06:09:29.6953208Z """ 2025-09-07T06:09:29.6953401Z 2025-09-07T06:09:29.6953571Z import json 2025-09-07T06:09:29.6953956Z import logging 2025-09-07T06:09:29.6954372Z import os 2025-09-07T06:09:29.6954754Z import random 2025-09-07T06:09:29.6955140Z import re 2025-09-07T06:09:29.6955525Z import sys 2025-09-07T06:09:29.6955949Z from argparse import ArgumentParser 2025-09-07T06:09:29.6956492Z from collections.abc import Iterable 2025-09-07T06:09:29.6957022Z from functools import cache 2025-09-07T06:09:29.6957501Z from logging import LogRecord 2025-09-07T06:09:29.6957999Z from typing import Any, NamedTuple 2025-09-07T06:09:29.6958530Z from urllib.request import Request, urlopen 2025-09-07T06:09:29.6958915Z 2025-09-07T06:09:29.6959081Z import yaml 2025-09-07T06:09:29.6959485Z from github import Auth, Github 2025-09-07T06:09:29.6960518Z from github.Issue import Issue 2025-09-07T06:09:29.6960846Z 2025-09-07T06:09:29.6960852Z 2025-09-07T06:09:29.6961085Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:29.6961780Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:29.6962674Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:29.6963236Z 2025-09-07T06:09:29.6963471Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:29.6964216Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:29.6964754Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:29.6965321Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:29.6965682Z 2025-09-07T06:09:29.6965887Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:29.6966220Z 2025-09-07T06:09:29.6966416Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:29.6966888Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:29.6967176Z 2025-09-07T06:09:29.6967183Z 2025-09-07T06:09:29.6967375Z class Experiment(NamedTuple): 2025-09-07T06:09:29.6967881Z rollout_perc: float = ( 2025-09-07T06:09:29.6968523Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:29.6969214Z ) 2025-09-07T06:09:29.6969591Z all_branches: bool = ( 2025-09-07T06:09:29.6970457Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:29.6971150Z ) 2025-09-07T06:09:29.6971528Z default: bool = ( 2025-09-07T06:09:29.6972115Z True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:29.6972765Z ) 2025-09-07T06:09:29.6972969Z 2025-09-07T06:09:29.6973151Z # Add more fields as needed 2025-09-07T06:09:29.6973458Z 2025-09-07T06:09:29.6973464Z 2025-09-07T06:09:29.6973659Z class Settings(NamedTuple): 2025-09-07T06:09:29.6974109Z """ 2025-09-07T06:09:29.6974576Z Settings for the experiments that can be opted into. 2025-09-07T06:09:29.6975148Z """ 2025-09-07T06:09:29.6975354Z 2025-09-07T06:09:29.6975575Z experiments: dict[str, Experiment] = {} 2025-09-07T06:09:29.6975947Z 2025-09-07T06:09:29.6975954Z 2025-09-07T06:09:29.6976170Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:29.6976806Z """Color codes the log messages based on the log level""" 2025-09-07T06:09:29.6977243Z 2025-09-07T06:09:29.6977412Z COLORS = { 2025-09-07T06:09:29.6977816Z "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:29.6978478Z "ERROR": "\033[31m", # Red 2025-09-07T06:09:29.6978995Z "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:29.6979514Z "INFO": "\033[0m", # Reset 2025-09-07T06:09:29.6980236Z "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:29.6980721Z } 2025-09-07T06:09:29.6980919Z 2025-09-07T06:09:29.6981139Z def format(self, record: LogRecord) -> str: 2025-09-07T06:09:29.6981897Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:29.6982688Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:29.6983274Z return super().format(record) 2025-09-07T06:09:29.6983612Z 2025-09-07T06:09:29.6983619Z 2025-09-07T06:09:29.6983827Z handler = logging.StreamHandler() 2025-09-07T06:09:29.6984540Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:29.6985113Z 2025-09-07T06:09:29.6985367Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:29.6985963Z log.addHandler(handler) 2025-09-07T06:09:29.6986428Z log.setLevel(logging.INFO) 2025-09-07T06:09:29.6986718Z 2025-09-07T06:09:29.6986725Z 2025-09-07T06:09:29.6986987Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:29.6987553Z """ 2025-09-07T06:09:29.6988069Z Defines outputs of the github action that invokes this script 2025-09-07T06:09:29.6988698Z """ 2025-09-07T06:09:29.6989077Z if not GITHUB_OUTPUT: 2025-09-07T06:09:29.6990401Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:29.6991552Z log.warning( 2025-09-07T06:09:29.6992409Z "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:29.6993345Z ) 2025-09-07T06:09:29.7003331Z print(f"::set-output name={key}::{value}") 2025-09-07T06:09:29.7003955Z return 2025-09-07T06:09:29.7004191Z 2025-09-07T06:09:29.7004598Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:29.7005208Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:29.7005797Z f.write(f"{key}={value}\n") 2025-09-07T06:09:29.7006126Z 2025-09-07T06:09:29.7006134Z 2025-09-07T06:09:29.7006450Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:29.7007089Z return frozenset( 2025-09-07T06:09:29.7007713Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:29.7008393Z ) 2025-09-07T06:09:29.7008598Z 2025-09-07T06:09:29.7008604Z 2025-09-07T06:09:29.7008790Z def parse_args() -> Any: 2025-09-07T06:09:29.7009347Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:29.7010438Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:29.7011232Z parser.add_argument( 2025-09-07T06:09:29.7011689Z "--github-issue-repo", 2025-09-07T06:09:29.7012164Z type=str, 2025-09-07T06:09:29.7012572Z required=False, 2025-09-07T06:09:29.7013027Z default="pytorch/test-infra", 2025-09-07T06:09:29.7013561Z help="GitHub repo to get the issue", 2025-09-07T06:09:29.7014080Z ) 2025-09-07T06:09:29.7014450Z parser.add_argument( 2025-09-07T06:09:29.7014907Z "--github-repo", 2025-09-07T06:09:29.7015359Z type=str, 2025-09-07T06:09:29.7015765Z required=True, 2025-09-07T06:09:29.7016227Z help="GitHub repo where CI is running", 2025-09-07T06:09:29.7016753Z ) 2025-09-07T06:09:29.7017130Z parser.add_argument( 2025-09-07T06:09:29.7017737Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:29.7018402Z ) 2025-09-07T06:09:29.7018776Z parser.add_argument( 2025-09-07T06:09:29.7071359Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:29.7072218Z ) 2025-09-07T06:09:29.7072632Z parser.add_argument( 2025-09-07T06:09:29.7073500Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:29.7074210Z ) 2025-09-07T06:09:29.7074592Z parser.add_argument( 2025-09-07T06:09:29.7075280Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:29.7076000Z ) 2025-09-07T06:09:29.7076385Z parser.add_argument( 2025-09-07T06:09:29.7076850Z "--github-ref-type", 2025-09-07T06:09:29.7077307Z type=str, 2025-09-07T06:09:29.7077717Z required=True, 2025-09-07T06:09:29.7078200Z help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:29.7078761Z ) 2025-09-07T06:09:29.7079138Z parser.add_argument( 2025-09-07T06:09:29.7079606Z "--eligible-experiments", 2025-09-07T06:09:29.7080265Z type=_str_comma_separated_to_set, 2025-09-07T06:09:29.7080801Z required=False, 2025-09-07T06:09:29.7081215Z default="", 2025-09-07T06:09:29.7082069Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:29.7082996Z ) 2025-09-07T06:09:29.7083363Z parser.add_argument( 2025-09-07T06:09:29.7083823Z "--opt-out-experiments", 2025-09-07T06:09:29.7084329Z type=_str_comma_separated_to_set, 2025-09-07T06:09:29.7084853Z required=False, 2025-09-07T06:09:29.7085269Z default="", 2025-09-07T06:09:29.7085666Z help=( 2025-09-07T06:09:29.7086322Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:29.7087463Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:29.7088293Z ), 2025-09-07T06:09:29.7088652Z ) 2025-09-07T06:09:29.7089026Z parser.add_argument( 2025-09-07T06:09:29.7089466Z "--pr-number", 2025-09-07T06:09:29.7089991Z type=str, 2025-09-07T06:09:29.7090392Z required=False, 2025-09-07T06:09:29.7090821Z default="", 2025-09-07T06:09:29.7091421Z help="the optional PR number where this is run", 2025-09-07T06:09:29.7092005Z ) 2025-09-07T06:09:29.7092210Z 2025-09-07T06:09:29.7092403Z return parser.parse_args() 2025-09-07T06:09:29.7092718Z 2025-09-07T06:09:29.7092725Z 2025-09-07T06:09:29.7093127Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:29.7093880Z auth = Auth.Token(github_token) 2025-09-07T06:09:29.7094400Z return Github(auth=auth) 2025-09-07T06:09:29.7094705Z 2025-09-07T06:09:29.7094713Z 2025-09-07T06:09:29.7095166Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:29.7095966Z repo = gh.get_repo(repo) 2025-09-07T06:09:29.7096476Z return repo.get_issue(number=issue_num) 2025-09-07T06:09:29.7096844Z 2025-09-07T06:09:29.7096850Z 2025-09-07T06:09:29.7097049Z def get_potential_pr_author( 2025-09-07T06:09:29.7097692Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:29.7098379Z ) -> str: 2025-09-07T06:09:29.7098891Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:29.7099689Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:29.7100548Z # embedded in the tag name: ciflow// 2025-09-07T06:09:29.7100974Z 2025-09-07T06:09:29.7101166Z gh = get_gh_client(github_token) 2025-09-07T06:09:29.7101499Z 2025-09-07T06:09:29.7101776Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:29.7102397Z split_tag = ref_name.split("/") 2025-09-07T06:09:29.7102899Z if ( 2025-09-07T06:09:29.7103291Z len(split_tag) == 3 2025-09-07T06:09:29.7103776Z and split_tag[0] == "ciflow" 2025-09-07T06:09:29.7104300Z and split_tag[2].isnumeric() 2025-09-07T06:09:29.7104794Z ): 2025-09-07T06:09:29.7105178Z pr_number = split_tag[2] 2025-09-07T06:09:29.7105796Z try: 2025-09-07T06:09:29.7106241Z repository = gh.get_repo(repo) 2025-09-07T06:09:29.7106852Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:29.7107456Z except Exception as e: 2025-09-07T06:09:29.7107974Z raise Exception( # noqa: TRY002 2025-09-07T06:09:29.7108638Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:29.7109273Z ) from e 2025-09-07T06:09:29.7110043Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:29.7110873Z # In all other cases, return the original input username 2025-09-07T06:09:29.7111462Z return username 2025-09-07T06:09:29.7111706Z 2025-09-07T06:09:29.7111713Z 2025-09-07T06:09:29.7111953Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:29.7112482Z """ 2025-09-07T06:09:29.7113123Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:29.7113902Z """ 2025-09-07T06:09:29.7114459Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:29.7114977Z 2025-09-07T06:09:29.7114984Z 2025-09-07T06:09:29.7115181Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:29.7115686Z try: 2025-09-07T06:09:29.7116080Z data = yaml.safe_load(yaml_text) 2025-09-07T06:09:29.7116589Z return data 2025-09-07T06:09:29.7117009Z except yaml.YAMLError: 2025-09-07T06:09:29.7117494Z log.exception("Error loading YAML") 2025-09-07T06:09:29.7118009Z raise 2025-09-07T06:09:29.7118231Z 2025-09-07T06:09:29.7118238Z 2025-09-07T06:09:29.7118656Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:29.7119400Z """ 2025-09-07T06:09:29.7120158Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:29.7120770Z 2025-09-07T06:09:29.7121274Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:29.7122043Z and the text below is the list of opted in users. 2025-09-07T06:09:29.7122443Z 2025-09-07T06:09:29.7122814Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:29.7123513Z """ 2025-09-07T06:09:29.7123959Z rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:29.7124581Z if len(rollout_state_parts) >= 2: 2025-09-07T06:09:29.7125182Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:29.7125759Z else: 2025-09-07T06:09:29.7126143Z return "", rollout_state 2025-09-07T06:09:29.7126450Z 2025-09-07T06:09:29.7126457Z 2025-09-07T06:09:29.7126658Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:29.7127170Z """ 2025-09-07T06:09:29.7127683Z Dictionary of users with a list of features they have opted into 2025-09-07T06:09:29.7128337Z """ 2025-09-07T06:09:29.7128541Z 2025-09-07T06:09:29.7128548Z 2025-09-07T06:09:29.7128892Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:29.7129528Z """ 2025-09-07T06:09:29.7130347Z 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:29.7131023Z 2025-09-07T06:09:29.7131632Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:29.7132620Z - Example line: "@User1,lf,split_build" 2025-09-07T06:09:29.7133296Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:29.7133778Z 2025-09-07T06:09:29.7133784Z 2025-09-07T06:09:29.7133942Z """ 2025-09-07T06:09:29.7134318Z optins = UserOptins() 2025-09-07T06:09:29.7134796Z for user in user_optin_text.split("\n"): 2025-09-07T06:09:29.7135349Z user = user.strip("\r\n\t -") 2025-09-07T06:09:29.7135884Z if not user or not user.startswith("@"): 2025-09-07T06:09:29.7136580Z # Not a valid user. Skip 2025-09-07T06:09:29.7137051Z continue 2025-09-07T06:09:29.7137304Z 2025-09-07T06:09:29.7137466Z if user: 2025-09-07T06:09:29.7137890Z usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:29.7138581Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:29.7139063Z 2025-09-07T06:09:29.7139232Z return optins 2025-09-07T06:09:29.7139468Z 2025-09-07T06:09:29.7139474Z 2025-09-07T06:09:29.7139871Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:29.7140493Z """ 2025-09-07T06:09:29.7140885Z Check if the experiment name is valid. 2025-09-07T06:09:29.7141400Z A valid name: 2025-09-07T06:09:29.7142020Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:29.7142980Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:29.7143705Z - Cannot contain spaces 2025-09-07T06:09:29.7144173Z """ 2025-09-07T06:09:29.7144368Z 2025-09-07T06:09:29.7144634Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:29.7145325Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:29.7145773Z 2025-09-07T06:09:29.7145934Z if valid: 2025-09-07T06:09:29.7146309Z return True 2025-09-07T06:09:29.7146553Z 2025-09-07T06:09:29.7146710Z log.error( 2025-09-07T06:09:29.7148166Z 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:29.7149707Z ) 2025-09-07T06:09:29.7150371Z return False 2025-09-07T06:09:29.7150619Z 2025-09-07T06:09:29.7150626Z 2025-09-07T06:09:29.7150941Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:29.7151591Z """ 2025-09-07T06:09:29.7152379Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:29.7153121Z """ 2025-09-07T06:09:29.7153474Z try: 2025-09-07T06:09:29.7153845Z if settings_text: 2025-09-07T06:09:29.7154568Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:29.7155348Z # for easy reading 2025-09-07T06:09:29.7156143Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:29.7157031Z # the backtick character in shell commands. 2025-09-07T06:09:29.7157636Z backtick = chr(96) # backtick character 2025-09-07T06:09:29.7158305Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:29.7158976Z settings = load_yaml(settings_text) 2025-09-07T06:09:29.7159358Z 2025-09-07T06:09:29.7159888Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:29.7160655Z experiments = {} 2025-09-07T06:09:29.7160967Z 2025-09-07T06:09:29.7161342Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:29.7162115Z if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:29.7163215Z # 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:29.7164257Z continue 2025-09-07T06:09:29.7164541Z 2025-09-07T06:09:29.7164726Z valid_settings = {} 2025-09-07T06:09:29.7165244Z for setting in exp_settings: 2025-09-07T06:09:29.7165824Z if setting not in Experiment._fields: 2025-09-07T06:09:29.7166375Z log.warning( 2025-09-07T06:09:29.7167078Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:29.7167936Z ) 2025-09-07T06:09:29.7168369Z else: 2025-09-07T06:09:29.7168866Z valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:29.7169286Z 2025-09-07T06:09:29.7169554Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:29.7170332Z return Settings(experiments) 2025-09-07T06:09:29.7170684Z 2025-09-07T06:09:29.7170854Z except Exception: 2025-09-07T06:09:29.7171328Z log.exception("Failed to parse settings") 2025-09-07T06:09:29.7171711Z 2025-09-07T06:09:29.7171881Z return Settings() 2025-09-07T06:09:29.7172147Z 2025-09-07T06:09:29.7172153Z 2025-09-07T06:09:29.7172400Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:29.7172959Z """ 2025-09-07T06:09:29.7173403Z Parse settings, if any, from the rollout state. 2025-09-07T06:09:29.7173809Z 2025-09-07T06:09:29.7174160Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:29.7174928Z and the text below is the list of opted in users. 2025-09-07T06:09:29.7175329Z 2025-09-07T06:09:29.7175736Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:29.7176460Z """ 2025-09-07T06:09:29.7177010Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:29.7177760Z return parse_settings_from_text(settings_text) 2025-09-07T06:09:29.7178161Z 2025-09-07T06:09:29.7178168Z 2025-09-07T06:09:29.7178411Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:29.7178972Z """ 2025-09-07T06:09:29.7179353Z Parse users from the rollout state. 2025-09-07T06:09:29.7179698Z 2025-09-07T06:09:29.7179958Z """ 2025-09-07T06:09:29.7180482Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:29.7181236Z return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:29.7181637Z 2025-09-07T06:09:29.7181643Z 2025-09-07T06:09:29.7182182Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:29.7182933Z """ 2025-09-07T06:09:29.7183352Z Check if a user is opted into an experiment 2025-09-07T06:09:29.7183883Z """ 2025-09-07T06:09:29.7184346Z return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:29.7184766Z 2025-09-07T06:09:29.7184772Z 2025-09-07T06:09:29.7185193Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:29.7185945Z """ 2025-09-07T06:09:29.7186407Z Check if a user explicitly opted out of an experiment 2025-09-07T06:09:29.7186988Z """ 2025-09-07T06:09:29.7187491Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:29.7188165Z experiment_optout = "-" + experiment_name 2025-09-07T06:09:29.7188805Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:29.7189406Z return False 2025-09-07T06:09:29.7189670Z 2025-09-07T06:09:29.7190245Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:29.7190881Z log.warning( 2025-09-07T06:09:29.7191696Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:29.7192578Z ) 2025-09-07T06:09:29.7192786Z 2025-09-07T06:09:29.7192948Z return True 2025-09-07T06:09:29.7193177Z 2025-09-07T06:09:29.7193183Z 2025-09-07T06:09:29.7193364Z def get_runner_prefix( 2025-09-07T06:09:29.7193800Z rollout_state: str, 2025-09-07T06:09:29.7194272Z workflow_requestors: Iterable[str], 2025-09-07T06:09:29.7194789Z branch: str, 2025-09-07T06:09:29.7195280Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:29.7195936Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:29.7196509Z is_canary: bool = False, 2025-09-07T06:09:29.7196961Z ) -> str: 2025-09-07T06:09:29.7197538Z settings = parse_settings(rollout_state) 2025-09-07T06:09:29.7198123Z user_optins = parse_users(rollout_state) 2025-09-07T06:09:29.7198490Z 2025-09-07T06:09:29.7198664Z fleet_prefix = "" 2025-09-07T06:09:29.7199087Z prefixes = [] 2025-09-07T06:09:29.7199708Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:29.7201074Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:29.7201799Z log.info( 2025-09-07T06:09:29.7202465Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:29.7203216Z ) 2025-09-07T06:09:29.7203586Z continue 2025-09-07T06:09:29.7203830Z 2025-09-07T06:09:29.7204026Z if opt_out_experiments: 2025-09-07T06:09:29.7204556Z if experiment_name in opt_out_experiments: 2025-09-07T06:09:29.7205191Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:29.7205778Z log.info( 2025-09-07T06:09:29.7206715Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:29.7207685Z ) 2025-09-07T06:09:29.7208068Z continue 2025-09-07T06:09:29.7208332Z 2025-09-07T06:09:29.7208523Z if eligible_experiments: 2025-09-07T06:09:29.7209101Z if experiment_name not in eligible_experiments: 2025-09-07T06:09:29.7209733Z exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:29.7210482Z log.info( 2025-09-07T06:09:29.7211269Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:29.7212095Z ) 2025-09-07T06:09:29.7212480Z continue 2025-09-07T06:09:29.7212948Z elif not experiment_settings.default: 2025-09-07T06:09:29.7213472Z log.info( 2025-09-07T06:09:29.7214267Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:29.7215001Z ) 2025-09-07T06:09:29.7215376Z continue 2025-09-07T06:09:29.7215621Z 2025-09-07T06:09:29.7215893Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:29.7216503Z opted_out_users = [ 2025-09-07T06:09:29.7216947Z requestor 2025-09-07T06:09:29.7217397Z for requestor in workflow_requestors 2025-09-07T06:09:29.7218064Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:29.7218682Z ] 2025-09-07T06:09:29.7218896Z 2025-09-07T06:09:29.7219072Z if opted_out_users: 2025-09-07T06:09:29.7219510Z log.info( 2025-09-07T06:09:29.7220232Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:29.7220925Z ) 2025-09-07T06:09:29.7221297Z continue 2025-09-07T06:09:29.7221550Z 2025-09-07T06:09:29.7221838Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:29.7222454Z opted_in_users = [ 2025-09-07T06:09:29.7222900Z requestor 2025-09-07T06:09:29.7223347Z for requestor in workflow_requestors 2025-09-07T06:09:29.7224010Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:29.7224615Z ] 2025-09-07T06:09:29.7224822Z 2025-09-07T06:09:29.7224990Z enabled = False 2025-09-07T06:09:29.7225425Z if opted_in_users: 2025-09-07T06:09:29.7225858Z log.info( 2025-09-07T06:09:29.7226457Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:29.7227130Z ) 2025-09-07T06:09:29.7227518Z enabled = True 2025-09-07T06:09:29.7227795Z 2025-09-07T06:09:29.7228009Z elif experiment_settings.rollout_perc: 2025-09-07T06:09:29.7228842Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:29.7230348Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:29.7231141Z log.info( 2025-09-07T06:09:29.7232508Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:29.7233538Z ) 2025-09-07T06:09:29.7233947Z enabled = True 2025-09-07T06:09:29.7234241Z 2025-09-07T06:09:29.7234404Z if enabled: 2025-09-07T06:09:29.7234831Z label = experiment_name 2025-09-07T06:09:29.7235379Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:29.7236214Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:29.7237083Z # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:29.7237838Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:29.7238488Z if is_canary: 2025-09-07T06:09:29.7238972Z label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:29.7239514Z fleet_prefix = label 2025-09-07T06:09:29.7240171Z else: 2025-09-07T06:09:29.7240597Z prefixes.append(label) 2025-09-07T06:09:29.7240938Z 2025-09-07T06:09:29.7241122Z if len(prefixes) > 1: 2025-09-07T06:09:29.7241550Z log.error( 2025-09-07T06:09:29.7242555Z 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:29.7243634Z ) 2025-09-07T06:09:29.7244020Z prefixes = prefixes[:1] 2025-09-07T06:09:29.7244319Z 2025-09-07T06:09:29.7244508Z # Fleet always comes first 2025-09-07T06:09:29.7244973Z if fleet_prefix: 2025-09-07T06:09:29.7245406Z prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:29.7245772Z 2025-09-07T06:09:29.7246194Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:29.7246612Z 2025-09-07T06:09:29.7246618Z 2025-09-07T06:09:29.7247059Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:29.7247812Z """ 2025-09-07T06:09:29.7248383Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:29.7248931Z 2025-09-07T06:09:29.7249310Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:29.7250125Z """ 2025-09-07T06:09:29.7250520Z gh = get_gh_client(github_token) 2025-09-07T06:09:29.7251048Z issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:29.7251694Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:29.7252137Z 2025-09-07T06:09:29.7252145Z 2025-09-07T06:09:29.7252529Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:29.7253288Z for _ in range(num_retries): 2025-09-07T06:09:29.7253755Z try: 2025-09-07T06:09:29.7254179Z req = Request(url=url, headers=headers) 2025-09-07T06:09:29.7254823Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:29.7255454Z return json.loads(content) 2025-09-07T06:09:29.7255972Z except Exception as e: 2025-09-07T06:09:29.7256491Z log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:29.7256880Z 2025-09-07T06:09:29.7257259Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:29.7257943Z return {} 2025-09-07T06:09:29.7258170Z 2025-09-07T06:09:29.7258176Z 2025-09-07T06:09:29.7258329Z @cache 2025-09-07T06:09:29.7258930Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:29.7259659Z """ 2025-09-07T06:09:29.7260163Z Dynamically get PR information 2025-09-07T06:09:29.7260636Z """ 2025-09-07T06:09:29.7261292Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:29.7261897Z headers = { 2025-09-07T06:09:29.7262348Z "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:29.7262928Z "Authorization": f"token {github_token}", 2025-09-07T06:09:29.7263452Z } 2025-09-07T06:09:29.7263864Z json_response: dict[str, Any] = download_json( 2025-09-07T06:09:29.7264454Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:29.7264984Z headers=headers, 2025-09-07T06:09:29.7265397Z ) 2025-09-07T06:09:29.7265592Z 2025-09-07T06:09:29.7265778Z if not json_response: 2025-09-07T06:09:29.7266326Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:29.7266949Z return {} 2025-09-07T06:09:29.7267180Z 2025-09-07T06:09:29.7267353Z return json_response 2025-09-07T06:09:29.7267634Z 2025-09-07T06:09:29.7267640Z 2025-09-07T06:09:29.7268033Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:29.7268788Z """ 2025-09-07T06:09:29.7269300Z Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:29.7270062Z """ 2025-09-07T06:09:29.7270539Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:29.7271137Z return { 2025-09-07T06:09:29.7271715Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:29.7272412Z } 2025-09-07T06:09:29.7272612Z 2025-09-07T06:09:29.7272618Z 2025-09-07T06:09:29.7272786Z def main() -> None: 2025-09-07T06:09:29.7273204Z args = parse_args() 2025-09-07T06:09:29.7273464Z 2025-09-07T06:09:29.7273686Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:29.7274061Z 2025-09-07T06:09:29.7274248Z # Check if the PR is opt-out 2025-09-07T06:09:29.7274731Z if args.pr_number: 2025-09-07T06:09:29.7275371Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:29.7276244Z if OPT_OUT_LABEL in labels: 2025-09-07T06:09:29.7276733Z log.info( 2025-09-07T06:09:29.7277413Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:29.7278165Z ) 2025-09-07T06:09:29.7278696Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:29.7279355Z sys.exit() 2025-09-07T06:09:29.7279609Z 2025-09-07T06:09:29.7279870Z try: 2025-09-07T06:09:29.7280310Z rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:29.7280997Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:29.7281617Z ) 2025-09-07T06:09:29.7281820Z 2025-09-07T06:09:29.7282018Z username = get_potential_pr_author( 2025-09-07T06:09:29.7282553Z args.github_token, 2025-09-07T06:09:29.7283022Z args.github_repo, 2025-09-07T06:09:29.7283480Z args.github_actor, 2025-09-07T06:09:29.7283960Z args.github_ref_type, 2025-09-07T06:09:29.7284450Z args.github_branch, 2025-09-07T06:09:29.7284912Z ) 2025-09-07T06:09:29.7285115Z 2025-09-07T06:09:29.7285397Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:29.7285856Z 2025-09-07T06:09:29.7286068Z runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:29.7286608Z rollout_state, 2025-09-07T06:09:29.7287097Z (args.github_issue_owner, username), 2025-09-07T06:09:29.7287642Z args.github_branch, 2025-09-07T06:09:29.7288126Z args.eligible_experiments, 2025-09-07T06:09:29.7288666Z args.opt_out_experiments, 2025-09-07T06:09:29.7289170Z is_canary, 2025-09-07T06:09:29.7289580Z ) 2025-09-07T06:09:29.7289883Z 2025-09-07T06:09:29.7290072Z except Exception as e: 2025-09-07T06:09:29.7290526Z log.error( 2025-09-07T06:09:29.7291192Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:29.7292089Z ) 2025-09-07T06:09:29.7292297Z 2025-09-07T06:09:29.7292626Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:29.7293114Z 2025-09-07T06:09:29.7293120Z 2025-09-07T06:09:29.7293293Z if __name__ == "__main__": 2025-09-07T06:09:29.7293727Z main() 2025-09-07T06:09:29.7293934Z 2025-09-07T06:09:29.7391545Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:29.7392422Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:29.7423479Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:29.7423951Z env: 2025-09-07T06:09:29.7424590Z GITHUB_TOKEN: *** 2025-09-07T06:09:29.7425019Z ISSUE_NUMBER: 5132 2025-09-07T06:09:29.7425467Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:29.7425961Z ISSUE_OWNER: 2025-09-07T06:09:29.7426363Z CHECK_EXPERIMENTS: 2025-09-07T06:09:29.7426803Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:29.7427235Z PR_NUMBER: 2025-09-07T06:09:29.7427617Z ##[endgroup] 2025-09-07T06:09:30.1500546Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T06:09:30.8721838Z Collecting urllib3==1.26.18 2025-09-07T06:09:30.9070229Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T06:09:30.9299241Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.1 MB/s eta 0:00:00 2025-09-07T06:09:30.9566290Z Collecting PyGithub==2.3.0 2025-09-07T06:09:30.9602182Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T06:09:31.0066825Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T06:09:31.0115553Z 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.0166199Z 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.0179604Z 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.0201602Z 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:31.0481364Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T06:09:31.0508721Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T06:09:31.0747277Z 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:31.1967085Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:31.1994176Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T06:09:31.3203378Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T06:09:31.3243897Z 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:31.3418457Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:31.3449546Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T06:09:31.3701221Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T06:09:31.3767000Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 27.8 MB/s eta 0:00:00 2025-09-07T06:09:31.3810949Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T06:09:31.3877697Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 71.3 MB/s eta 0:00:00 2025-09-07T06:09:31.3913149Z 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:31.4002132Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 120.4 MB/s eta 0:00:00 2025-09-07T06:09:31.4030744Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T06:09:31.4085181Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T06:09:31.4152019Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 94.6 MB/s eta 0:00:00 2025-09-07T06:09:31.4187618Z 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:31.4233163Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 27.5 MB/s eta 0:00:00 2025-09-07T06:09:31.4259592Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T06:09:31.4304163Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 39.2 MB/s eta 0:00:00 2025-09-07T06:09:31.7201796Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T06:09:32.2529506Z 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:32.3391377Z ##[group]Run curr_branch="main" 2025-09-07T06:09:32.3391698Z curr_branch="main" 2025-09-07T06:09:32.3391934Z curr_ref_type="branch" 2025-09-07T06:09:32.3392223Z echo "Current branch is '$curr_branch'" 2025-09-07T06:09:32.3392472Z  2025-09-07T06:09:32.3392668Z python3 runner_determinator.py \ 2025-09-07T06:09:32.3392942Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T06:09:32.3393209Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T06:09:32.3393487Z  --github-branch "$curr_branch" \ 2025-09-07T06:09:32.3393786Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T06:09:32.3394068Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T06:09:32.3394339Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T06:09:32.3394608Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T06:09:32.3394899Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T06:09:32.3395259Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T06:09:32.3395545Z  --pr-number "${PR_NUMBER}" 2025-09-07T06:09:32.3427623Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:32.3427856Z env: 2025-09-07T06:09:32.3428413Z GITHUB_TOKEN: *** 2025-09-07T06:09:32.3428624Z ISSUE_NUMBER: 5132 2025-09-07T06:09:32.3428833Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:32.3429067Z ISSUE_OWNER: 2025-09-07T06:09:32.3429244Z CHECK_EXPERIMENTS: 2025-09-07T06:09:32.3429436Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:32.3429614Z PR_NUMBER: 2025-09-07T06:09:32.3429998Z ##[endgroup] 2025-09-07T06:09:32.3481183Z Current branch is 'main' 2025-09-07T06:09:33.9852817Z INFO : Based on rollout percentage of 60%, enabling experiment lf. 2025-09-07T06:09:33.9854019Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T06:09:33.9854825Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T06:09:33.9855599Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T06:09:33.9856234Z INFO : Setting output: label-type='lf.' 2025-09-07T06:09:34.0175031Z Evaluate and set job outputs 2025-09-07T06:09:34.0181815Z Set output 'label-type' 2025-09-07T06:09:34.0183613Z Cleaning up orphan processes