2025-08-14T20:43:47.2016241Z Current runner version: '2.327.1' 2025-08-14T20:43:47.2046604Z ##[group]Runner Image Provisioner 2025-08-14T20:43:47.2047807Z Hosted Compute Agent 2025-08-14T20:43:47.2048661Z Version: 20250812.370 2025-08-14T20:43:47.2049591Z Commit: 4a2b2bf7520004e3e907c2150c8cabe342a3da32 2025-08-14T20:43:47.2050945Z Build Date: 2025-08-12T16:08:14Z 2025-08-14T20:43:47.2051844Z ##[endgroup] 2025-08-14T20:43:47.2052712Z ##[group]Operating System 2025-08-14T20:43:47.2053580Z Ubuntu 2025-08-14T20:43:47.2054337Z 24.04.2 2025-08-14T20:43:47.2055046Z LTS 2025-08-14T20:43:47.2055818Z ##[endgroup] 2025-08-14T20:43:47.2056539Z ##[group]Runner Image 2025-08-14T20:43:47.2057431Z Image: ubuntu-24.04 2025-08-14T20:43:47.2058337Z Version: 20250810.1.0 2025-08-14T20:43:47.2060134Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250810.1/images/ubuntu/Ubuntu2404-Readme.md 2025-08-14T20:43:47.2062748Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250810.1 2025-08-14T20:43:47.2064298Z ##[endgroup] 2025-08-14T20:43:47.2065980Z ##[group]GITHUB_TOKEN Permissions 2025-08-14T20:43:47.2068800Z Contents: read 2025-08-14T20:43:47.2069545Z Metadata: read 2025-08-14T20:43:47.2071033Z ##[endgroup] 2025-08-14T20:43:47.2074095Z Secret source: Actions 2025-08-14T20:43:47.2075603Z Prepare workflow directory 2025-08-14T20:43:47.2783921Z Prepare all required actions 2025-08-14T20:43:47.2863771Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (1fc683cf17c8c673044538d10266c00f92987be2) 2025-08-14T20:43:47.2871220Z ##[group] Inputs 2025-08-14T20:43:47.2872055Z check_experiments: 2025-08-14T20:43:47.2872915Z opt_out_experiments: lf 2025-08-14T20:43:47.2873820Z triggering_actor: pytorchmergebot 2025-08-14T20:43:47.2874986Z issue_owner: 2025-08-14T20:43:47.2875693Z curr_branch: main 2025-08-14T20:43:47.2876477Z curr_ref_type: branch 2025-08-14T20:43:47.2877564Z issue_number: 5132 2025-08-14T20:43:47.2878377Z ##[endgroup] 2025-08-14T20:43:47.2879395Z Complete job name: get-label-type / runner-determinator 2025-08-14T20:43:47.7907077Z ##[group]Run cat < runner_determinator.py 2025-08-14T20:43:47.7909594Z cat < runner_determinator.py 2025-08-14T20:43:47.7910498Z # flake8: noqa: G004 2025-08-14T20:43:47.7911098Z  2025-08-14T20:43:47.7911863Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:43:47.7913039Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:43:47.7914053Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:43:47.7914759Z  2025-08-14T20:43:47.7915267Z """ 2025-08-14T20:43:47.7915995Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:43:47.7917041Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:43:47.7918273Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:43:47.7919271Z of which runners should be used to run which job. 2025-08-14T20:43:47.7920094Z  2025-08-14T20:43:47.7920922Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:43:47.7922010Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:43:47.7923018Z settings are considered to be empty with only the second part, the user 2025-08-14T20:43:47.7923940Z list, defined. 2025-08-14T20:43:47.7924444Z  2025-08-14T20:43:47.7925151Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:43:47.7926287Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:43:47.7927267Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:43:47.7928011Z  2025-08-14T20:43:47.7929000Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:43:47.7930190Z The user list is also a comma separated list of additional features or 2025-08-14T20:43:47.7931111Z experiments which the user could be opted in to. 2025-08-14T20:43:47.7931808Z  2025-08-14T20:43:47.7932346Z The user list has the following rules: 2025-08-14T20:43:47.7932949Z  2025-08-14T20:43:47.7933692Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:43:47.7934741Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:43:47.7935690Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:43:47.7936403Z  2025-08-14T20:43:47.7936866Z Example config: 2025-08-14T20:43:47.7937527Z  # A list of experiments that can be opted into. 2025-08-14T20:43:47.7938305Z  # This defines the behavior they'll induce when opted into. 2025-08-14T20:43:47.7939154Z  # Expected syntax is: 2025-08-14T20:43:47.7940250Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:43:47.7941701Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:43:47.8017264Z  2025-08-14T20:43:47.8017692Z  experiments: 2025-08-14T20:43:47.8018139Z  lf: 2025-08-14T20:43:47.8018569Z  rollout_percent: 25 2025-08-14T20:43:47.8019085Z  all_branches: false 2025-08-14T20:43:47.8019611Z  default: true 2025-08-14T20:43:47.8020191Z  --- 2025-08-14T20:43:47.8020581Z  2025-08-14T20:43:47.8020945Z  # Opt-ins: 2025-08-14T20:43:47.8021664Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:43:47.8022900Z  # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:43:47.8023837Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:43:47.8024624Z  # Experiments should be from the above list. 2025-08-14T20:43:47.8025198Z  2025-08-14T20:43:47.8025604Z  @User1,-lf,split_build 2025-08-14T20:43:47.8026107Z  @User2,lf 2025-08-14T20:43:47.8026561Z  @User3,split_build 2025-08-14T20:43:47.8027041Z """ 2025-08-14T20:43:47.8027408Z  2025-08-14T20:43:47.8027780Z import json 2025-08-14T20:43:47.8028201Z import logging 2025-08-14T20:43:47.8028635Z import os 2025-08-14T20:43:47.8029047Z import random 2025-08-14T20:43:47.8029482Z import re 2025-08-14T20:43:47.8029880Z import sys 2025-08-14T20:43:47.8030638Z from argparse import ArgumentParser 2025-08-14T20:43:47.8031319Z from collections.abc import Iterable 2025-08-14T20:43:47.8031905Z from functools import cache 2025-08-14T20:43:47.8032438Z from logging import LogRecord 2025-08-14T20:43:47.8033038Z from typing import Any, NamedTuple 2025-08-14T20:43:47.8033708Z from urllib.request import Request, urlopen 2025-08-14T20:43:47.8034288Z  2025-08-14T20:43:47.8034665Z import yaml 2025-08-14T20:43:47.8035129Z from github import Auth, Github 2025-08-14T20:43:47.8035682Z from github.Issue import Issue 2025-08-14T20:43:47.8036198Z  2025-08-14T20:43:47.8036556Z  2025-08-14T20:43:47.8037011Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:43:47.8037771Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:43:47.8038750Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:43:47.8039507Z  2025-08-14T20:43:47.8040283Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:43:47.8040924Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:43:47.8041513Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:43:47.8042163Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:43:47.8042723Z  2025-08-14T20:43:47.8043162Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:43:47.8043700Z  2025-08-14T20:43:47.8044087Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:43:47.8044609Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:43:47.8045100Z  2025-08-14T20:43:47.8045463Z  2025-08-14T20:43:47.8045865Z class Experiment(NamedTuple): 2025-08-14T20:43:47.8046417Z  rollout_perc: float = ( 2025-08-14T20:43:47.8047143Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:43:47.8047874Z  ) 2025-08-14T20:43:47.8048293Z  all_branches: bool = ( 2025-08-14T20:43:47.8049018Z  False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:43:47.8049734Z  ) 2025-08-14T20:43:47.8050233Z  default: bool = ( 2025-08-14T20:43:47.8050895Z  True # If True, the experiment is enabled by default for all queries 2025-08-14T20:43:47.8051577Z  ) 2025-08-14T20:43:47.8051957Z  2025-08-14T20:43:47.8052412Z  # Add more fields as needed 2025-08-14T20:43:47.8052969Z  2025-08-14T20:43:47.8053379Z  2025-08-14T20:43:47.8053815Z class Settings(NamedTuple): 2025-08-14T20:43:47.8054360Z  """ 2025-08-14T20:43:47.8054880Z  Settings for the experiments that can be opted into. 2025-08-14T20:43:47.8055501Z  """ 2025-08-14T20:43:47.8055883Z  2025-08-14T20:43:47.8056309Z  experiments: dict[str, Experiment] = {} 2025-08-14T20:43:47.8056854Z  2025-08-14T20:43:47.8057351Z  2025-08-14T20:43:47.8057804Z class ColorFormatter(logging.Formatter): 2025-08-14T20:43:47.8058506Z  """Color codes the log messages based on the log level""" 2025-08-14T20:43:47.8059127Z  2025-08-14T20:43:47.8059494Z  COLORS = { 2025-08-14T20:43:47.8060111Z  "WARNING": "\033[33m", # Yellow 2025-08-14T20:43:47.8060733Z  "ERROR": "\033[31m", # Red 2025-08-14T20:43:47.8061329Z  "CRITICAL": "\033[31m", # Red 2025-08-14T20:43:47.8061875Z  "INFO": "\033[0m", # Reset 2025-08-14T20:43:47.8062425Z  "DEBUG": "\033[0m", # Reset 2025-08-14T20:43:47.8062941Z  } 2025-08-14T20:43:47.8063318Z  2025-08-14T20:43:47.8063791Z  def format(self, record: LogRecord) -> str: 2025-08-14T20:43:47.8064602Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:43:47.8065447Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:43:47.8066133Z  return super().format(record) 2025-08-14T20:43:47.8066706Z  2025-08-14T20:43:47.8067126Z  2025-08-14T20:43:47.8067584Z handler = logging.StreamHandler() 2025-08-14T20:43:47.8068423Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:43:47.8069227Z  2025-08-14T20:43:47.8069768Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:43:47.8070531Z log.addHandler(handler) 2025-08-14T20:43:47.8071038Z log.setLevel(logging.INFO) 2025-08-14T20:43:47.8071532Z  2025-08-14T20:43:47.8071888Z  2025-08-14T20:43:47.8072384Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:43:47.8072995Z  """ 2025-08-14T20:43:47.8073566Z  Defines outputs of the github action that invokes this script 2025-08-14T20:43:47.8074372Z  """ 2025-08-14T20:43:47.8074782Z  if not GITHUB_OUTPUT: 2025-08-14T20:43:47.8075931Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:43:47.8077113Z  log.warning( 2025-08-14T20:43:47.8078054Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:43:47.8079026Z  ) 2025-08-14T20:43:47.8079520Z  print(f"::set-output name={key}::{value}") 2025-08-14T20:43:47.8080275Z  return 2025-08-14T20:43:47.8080709Z  2025-08-14T20:43:47.8081136Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:43:47.8081768Z  log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:43:47.8082393Z  f.write(f"{key}={value}\n") 2025-08-14T20:43:47.8082924Z  2025-08-14T20:43:47.8083300Z  2025-08-14T20:43:47.8083847Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:43:47.8084555Z  return frozenset( 2025-08-14T20:43:47.8085249Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:43:47.8085967Z  ) 2025-08-14T20:43:47.8086354Z  2025-08-14T20:43:47.8086713Z  2025-08-14T20:43:47.8087104Z def parse_args() -> Any: 2025-08-14T20:43:47.8087756Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:43:47.8088701Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:43:47.8089521Z  parser.add_argument( 2025-08-14T20:43:47.8090160Z  "--github-issue-repo", 2025-08-14T20:43:47.8090706Z  type=str, 2025-08-14T20:43:47.8091172Z  required=False, 2025-08-14T20:43:47.8091828Z  default="pytorch/test-infra", 2025-08-14T20:43:47.8092444Z  help="GitHub repo to get the issue", 2025-08-14T20:43:47.8093000Z  ) 2025-08-14T20:43:47.8093423Z  parser.add_argument( 2025-08-14T20:43:47.8093952Z  "--github-repo", 2025-08-14T20:43:47.8094449Z  type=str, 2025-08-14T20:43:47.8094923Z  required=True, 2025-08-14T20:43:47.8095474Z  help="GitHub repo where CI is running", 2025-08-14T20:43:47.8096036Z  ) 2025-08-14T20:43:47.8096458Z  parser.add_argument( 2025-08-14T20:43:47.8097146Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:43:47.8097852Z  ) 2025-08-14T20:43:47.8098265Z  parser.add_argument( 2025-08-14T20:43:47.8098976Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:43:47.8099712Z  ) 2025-08-14T20:43:47.8100274Z  parser.add_argument( 2025-08-14T20:43:47.8101005Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:43:47.8101738Z  ) 2025-08-14T20:43:47.8102164Z  parser.add_argument( 2025-08-14T20:43:47.8102915Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:43:47.8103680Z  ) 2025-08-14T20:43:47.8104116Z  parser.add_argument( 2025-08-14T20:43:47.8104632Z  "--github-ref-type", 2025-08-14T20:43:47.8105152Z  type=str, 2025-08-14T20:43:47.8105624Z  required=True, 2025-08-14T20:43:47.8106207Z  help="Current GitHub ref type, branch or tag", 2025-08-14T20:43:47.8106809Z  ) 2025-08-14T20:43:47.8107232Z  parser.add_argument( 2025-08-14T20:43:47.8107780Z  "--eligible-experiments", 2025-08-14T20:43:47.8108504Z  type=_str_comma_separated_to_set, 2025-08-14T20:43:47.8109067Z  required=False, 2025-08-14T20:43:47.8109552Z  default="", 2025-08-14T20:43:47.8110763Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:43:47.8111754Z  ) 2025-08-14T20:43:47.8112171Z  parser.add_argument( 2025-08-14T20:43:47.8112704Z  "--opt-out-experiments", 2025-08-14T20:43:47.8113287Z  type=_str_comma_separated_to_set, 2025-08-14T20:43:47.8113853Z  required=False, 2025-08-14T20:43:47.8114345Z  default="", 2025-08-14T20:43:47.8114804Z  help=( 2025-08-14T20:43:47.8115561Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:43:47.8116774Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:43:47.8117670Z  ), 2025-08-14T20:43:47.8118082Z  ) 2025-08-14T20:43:47.8118496Z  parser.add_argument( 2025-08-14T20:43:47.8119007Z  "--pr-number", 2025-08-14T20:43:47.8119487Z  type=str, 2025-08-14T20:43:47.8120109Z  required=False, 2025-08-14T20:43:47.8120610Z  default="", 2025-08-14T20:43:47.8121174Z  help="the optional PR number where this is run", 2025-08-14T20:43:47.8121776Z  ) 2025-08-14T20:43:47.8122167Z  2025-08-14T20:43:47.8122585Z  return parser.parse_args() 2025-08-14T20:43:47.8123102Z  2025-08-14T20:43:47.8123474Z  2025-08-14T20:43:47.8124121Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.8125079Z  auth = Auth.Token(github_token) 2025-08-14T20:43:47.8125675Z  return Github(auth=auth) 2025-08-14T20:43:47.8126191Z  2025-08-14T20:43:47.8126557Z  2025-08-14T20:43:47.8127271Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.8128138Z  repo = gh.get_repo(repo) 2025-08-14T20:43:47.8128714Z  return repo.get_issue(number=issue_num) 2025-08-14T20:43:47.8129284Z  2025-08-14T20:43:47.8129650Z  2025-08-14T20:43:47.8130157Z def get_potential_pr_author( 2025-08-14T20:43:47.8130919Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:43:47.8131694Z ) -> str: 2025-08-14T20:43:47.8132334Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:43:47.8133215Z  # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:43:47.8134045Z  # embedded in the tag name: ciflow// 2025-08-14T20:43:47.8134652Z  2025-08-14T20:43:47.8135077Z  gh = get_gh_client(github_token) 2025-08-14T20:43:47.8135609Z  2025-08-14T20:43:47.8136121Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:43:47.8136810Z  split_tag = ref_name.split("/") 2025-08-14T20:43:47.8137357Z  if ( 2025-08-14T20:43:47.8137807Z  len(split_tag) == 3 2025-08-14T20:43:47.8138359Z  and split_tag[0] == "ciflow" 2025-08-14T20:43:47.8138945Z  and split_tag[2].isnumeric() 2025-08-14T20:43:47.8139484Z  ): 2025-08-14T20:43:47.8140087Z  pr_number = split_tag[2] 2025-08-14T20:43:47.8140633Z  try: 2025-08-14T20:43:47.8141136Z  repository = gh.get_repo(repo) 2025-08-14T20:43:47.8141951Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:43:47.8142619Z  except Exception as e: 2025-08-14T20:43:47.8143211Z  raise Exception( # noqa: TRY002 2025-08-14T20:43:47.8143942Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:43:47.8144643Z  ) from e 2025-08-14T20:43:47.8145266Z  return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:43:47.8146032Z  # In all other cases, return the original input username 2025-08-14T20:43:47.8146676Z  return username 2025-08-14T20:43:47.8147136Z  2025-08-14T20:43:47.8147503Z  2025-08-14T20:43:47.8147963Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:43:47.8148553Z  """ 2025-08-14T20:43:47.8149277Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:43:47.8150221Z  """ 2025-08-14T20:43:47.8150835Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:43:47.8151550Z  2025-08-14T20:43:47.8151924Z  2025-08-14T20:43:47.8152342Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:43:47.8152892Z  try: 2025-08-14T20:43:47.8153337Z  data = yaml.safe_load(yaml_text) 2025-08-14T20:43:47.8153895Z  return data 2025-08-14T20:43:47.8154389Z  except yaml.YAMLError: 2025-08-14T20:43:47.8154950Z  log.exception("Error loading YAML") 2025-08-14T20:43:47.8155511Z  raise 2025-08-14T20:43:47.8155932Z  2025-08-14T20:43:47.8156300Z  2025-08-14T20:43:47.8156961Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:43:47.8157755Z  """ 2025-08-14T20:43:47.8158579Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:43:47.8159380Z  2025-08-14T20:43:47.8160069Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.8160923Z  and the text below is the list of opted in users. 2025-08-14T20:43:47.8161527Z  2025-08-14T20:43:47.8162144Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:43:47.8162898Z  """ 2025-08-14T20:43:47.8163409Z  rollout_state_parts = rollout_state.split("---") 2025-08-14T20:43:47.8164104Z  if len(rollout_state_parts) >= 2: 2025-08-14T20:43:47.8164781Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:43:47.8165612Z  else: 2025-08-14T20:43:47.8166101Z  return "", rollout_state 2025-08-14T20:43:47.8166613Z  2025-08-14T20:43:47.8166977Z  2025-08-14T20:43:47.8167412Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:43:47.8167971Z  """ 2025-08-14T20:43:47.8168554Z  Dictionary of users with a list of features they have opted into 2025-08-14T20:43:47.8169237Z  """ 2025-08-14T20:43:47.8169621Z  2025-08-14T20:43:47.8170079Z  2025-08-14T20:43:47.8170650Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:43:47.8171345Z  """ 2025-08-14T20:43:47.8172120Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:43:47.8172998Z  2025-08-14T20:43:47.8173865Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:43:47.8174925Z  - Example line: "@User1,lf,split_build" 2025-08-14T20:43:47.8175808Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:43:47.8176482Z  2025-08-14T20:43:47.8176835Z  2025-08-14T20:43:47.8177191Z  """ 2025-08-14T20:43:47.8177607Z  optins = UserOptins() 2025-08-14T20:43:47.8178169Z  for user in user_optin_text.split("\n"): 2025-08-14T20:43:47.8178783Z  user = user.strip("\r\n\t -") 2025-08-14T20:43:47.8179389Z  if not user or not user.startswith("@"): 2025-08-14T20:43:47.8180141Z  # Not a valid user. Skip 2025-08-14T20:43:47.8180685Z  continue 2025-08-14T20:43:47.8181135Z  2025-08-14T20:43:47.8181536Z  if user: 2025-08-14T20:43:47.8182053Z  usr_name = user.split(",")[0].strip("@") 2025-08-14T20:43:47.8182799Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:43:47.8183485Z  2025-08-14T20:43:47.8183896Z  return optins 2025-08-14T20:43:47.8184362Z  2025-08-14T20:43:47.8184712Z  2025-08-14T20:43:47.8185266Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:43:47.8185946Z  """ 2025-08-14T20:43:47.8186420Z  Check if the experiment name is valid. 2025-08-14T20:43:47.8186976Z  A valid name: 2025-08-14T20:43:47.8187727Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:43:47.8188769Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:43:47.8189557Z  - Cannot contain spaces 2025-08-14T20:43:47.8190602Z  """ 2025-08-14T20:43:47.8191001Z  2025-08-14T20:43:47.8191544Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:43:47.8192321Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:43:47.8193247Z  2025-08-14T20:43:47.8193830Z  if valid: 2025-08-14T20:43:47.8194279Z  return True 2025-08-14T20:43:47.8194748Z  2025-08-14T20:43:47.8195116Z  log.error( 2025-08-14T20:43:47.8196685Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-14T20:43:47.8198339Z  ) 2025-08-14T20:43:47.8198761Z  return False 2025-08-14T20:43:47.8199204Z  2025-08-14T20:43:47.8199559Z  2025-08-14T20:43:47.8200225Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:43:47.8200905Z  """ 2025-08-14T20:43:47.8201557Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:43:47.8202332Z  """ 2025-08-14T20:43:47.8202725Z  try: 2025-08-14T20:43:47.8203131Z  if settings_text: 2025-08-14T20:43:47.8203938Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:43:47.8204773Z  # for easy reading 2025-08-14T20:43:47.8205648Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:43:47.8206599Z  # the backtick character in shell commands. 2025-08-14T20:43:47.8207252Z  backtick = chr(96) # backtick character 2025-08-14T20:43:47.8207989Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:43:47.8208703Z  settings = load_yaml(settings_text) 2025-08-14T20:43:47.8209248Z  2025-08-14T20:43:47.8209879Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:43:47.8210930Z  experiments = {} 2025-08-14T20:43:47.8211431Z  2025-08-14T20:43:47.8212032Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:43:47.8212849Z  if not is_valid_experiment_name(exp_name): 2025-08-14T20:43:47.8214016Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-14T20:43:47.8215106Z  continue 2025-08-14T20:43:47.8215596Z  2025-08-14T20:43:47.8215994Z  valid_settings = {} 2025-08-14T20:43:47.8216566Z  for setting in exp_settings: 2025-08-14T20:43:47.8217182Z  if setting not in Experiment._fields: 2025-08-14T20:43:47.8217787Z  log.warning( 2025-08-14T20:43:47.8218572Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:43:47.8219322Z  ) 2025-08-14T20:43:47.8219804Z  else: 2025-08-14T20:43:47.8220497Z  valid_settings[setting] = exp_settings[setting] 2025-08-14T20:43:47.8221100Z  2025-08-14T20:43:47.8221606Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:43:47.8222289Z  return Settings(experiments) 2025-08-14T20:43:47.8222819Z  2025-08-14T20:43:47.8223200Z  except Exception: 2025-08-14T20:43:47.8223770Z  log.exception("Failed to parse settings") 2025-08-14T20:43:47.8224336Z  2025-08-14T20:43:47.8224720Z  return Settings() 2025-08-14T20:43:47.8225178Z  2025-08-14T20:43:47.8225535Z  2025-08-14T20:43:47.8226143Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:43:47.8226769Z  """ 2025-08-14T20:43:47.8227251Z  Parse settings, if any, from the rollout state. 2025-08-14T20:43:47.8227839Z  2025-08-14T20:43:47.8228419Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.8229236Z  and the text below is the list of opted in users. 2025-08-14T20:43:47.8229824Z  2025-08-14T20:43:47.8230563Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:43:47.8231330Z  """ 2025-08-14T20:43:47.8231935Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.8232755Z  return parse_settings_from_text(settings_text) 2025-08-14T20:43:47.8233330Z  2025-08-14T20:43:47.8233690Z  2025-08-14T20:43:47.8234182Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:43:47.8234783Z  """ 2025-08-14T20:43:47.8235227Z  Parse users from the rollout state. 2025-08-14T20:43:47.8235762Z  2025-08-14T20:43:47.8236125Z  """ 2025-08-14T20:43:47.8236717Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.8237518Z  return parse_user_opt_in_from_text(users_text) 2025-08-14T20:43:47.8238102Z  2025-08-14T20:43:47.8238453Z  2025-08-14T20:43:47.8239109Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.8239893Z  """ 2025-08-14T20:43:47.8240495Z  Check if a user is opted into an experiment 2025-08-14T20:43:47.8241068Z  """ 2025-08-14T20:43:47.8241590Z  return experiment_name in user_optins.get(user, []) 2025-08-14T20:43:47.8242200Z  2025-08-14T20:43:47.8242682Z  2025-08-14T20:43:47.8243366Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.8244162Z  """ 2025-08-14T20:43:47.8244678Z  Check if a user explicitly opted out of an experiment 2025-08-14T20:43:47.8245289Z  """ 2025-08-14T20:43:47.8245856Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:43:47.8246607Z  experiment_optout = "-" + experiment_name 2025-08-14T20:43:47.8247314Z  if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:43:47.8247957Z  return False 2025-08-14T20:43:47.8248406Z  2025-08-14T20:43:47.8248904Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:43:47.8249540Z  log.warning( 2025-08-14T20:43:47.8250544Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:43:47.8251476Z  ) 2025-08-14T20:43:47.8251868Z  2025-08-14T20:43:47.8252243Z  return True 2025-08-14T20:43:47.8252680Z  2025-08-14T20:43:47.8253042Z  2025-08-14T20:43:47.8253421Z def get_runner_prefix( 2025-08-14T20:43:47.8253925Z  rollout_state: str, 2025-08-14T20:43:47.8254452Z  workflow_requestors: Iterable[str], 2025-08-14T20:43:47.8255014Z  branch: str, 2025-08-14T20:43:47.8255589Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.8256316Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.8256945Z  is_canary: bool = False, 2025-08-14T20:43:47.8257443Z ) -> str: 2025-08-14T20:43:47.8257938Z  settings = parse_settings(rollout_state) 2025-08-14T20:43:47.8258583Z  user_optins = parse_users(rollout_state) 2025-08-14T20:43:47.8259152Z  2025-08-14T20:43:47.8259659Z  fleet_prefix = "" 2025-08-14T20:43:47.8260260Z  prefixes = [] 2025-08-14T20:43:47.8260982Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:43:47.8261988Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:43:47.8262752Z  log.info( 2025-08-14T20:43:47.8263502Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:43:47.8264328Z  ) 2025-08-14T20:43:47.8264773Z  continue 2025-08-14T20:43:47.8265215Z  2025-08-14T20:43:47.8265612Z  if opt_out_experiments: 2025-08-14T20:43:47.8266205Z  if experiment_name in opt_out_experiments: 2025-08-14T20:43:47.8266904Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:43:47.8267545Z  log.info( 2025-08-14T20:43:47.8268555Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:43:47.8269569Z  ) 2025-08-14T20:43:47.8270123Z  continue 2025-08-14T20:43:47.8270596Z  2025-08-14T20:43:47.8270996Z  if eligible_experiments: 2025-08-14T20:43:47.8271631Z  if experiment_name not in eligible_experiments: 2025-08-14T20:43:47.8272318Z  exp_list = ", ".join(eligible_experiments) 2025-08-14T20:43:47.8272950Z  log.info( 2025-08-14T20:43:47.8273820Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:43:47.8274691Z  ) 2025-08-14T20:43:47.8275151Z  continue 2025-08-14T20:43:47.8275826Z  elif not experiment_settings.default: 2025-08-14T20:43:47.8276399Z  log.info( 2025-08-14T20:43:47.8277120Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:43:47.8277889Z  ) 2025-08-14T20:43:47.8278328Z  continue 2025-08-14T20:43:47.8278771Z  2025-08-14T20:43:47.8279276Z  # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:43:47.8280030Z  opted_out_users = [ 2025-08-14T20:43:47.8280554Z  requestor 2025-08-14T20:43:47.8281079Z  for requestor in workflow_requestors 2025-08-14T20:43:47.8281805Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.8282470Z  ] 2025-08-14T20:43:47.8282869Z  2025-08-14T20:43:47.8283256Z  if opted_out_users: 2025-08-14T20:43:47.8283787Z  log.info( 2025-08-14T20:43:47.8284488Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:43:47.8285226Z  ) 2025-08-14T20:43:47.8285664Z  continue 2025-08-14T20:43:47.8286118Z  2025-08-14T20:43:47.8286616Z  # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:43:47.8287278Z  opted_in_users = [ 2025-08-14T20:43:47.8287790Z  requestor 2025-08-14T20:43:47.8288326Z  for requestor in workflow_requestors 2025-08-14T20:43:47.8289046Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.8289716Z  ] 2025-08-14T20:43:47.8290221Z  2025-08-14T20:43:47.8290601Z  enabled = False 2025-08-14T20:43:47.8291107Z  if opted_in_users: 2025-08-14T20:43:47.8291738Z  log.info( 2025-08-14T20:43:47.8292440Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:43:47.8293159Z  ) 2025-08-14T20:43:47.8293617Z  enabled = True 2025-08-14T20:43:47.8294104Z  2025-08-14T20:43:47.8294549Z  elif experiment_settings.rollout_perc: 2025-08-14T20:43:47.8295431Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:43:47.8296424Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:43:47.8297115Z  log.info( 2025-08-14T20:43:47.8298054Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:43:47.8299024Z  ) 2025-08-14T20:43:47.8299503Z  enabled = True 2025-08-14T20:43:47.8300102Z  2025-08-14T20:43:47.8300491Z  if enabled: 2025-08-14T20:43:47.8300981Z  label = experiment_name 2025-08-14T20:43:47.8301591Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:43:47.8302468Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:43:47.8303408Z  # - If it's enabled, then we always list it's prefix first 2025-08-14T20:43:47.8304236Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:43:47.8304934Z  if is_canary: 2025-08-14T20:43:47.8305497Z  label += CANARY_FLEET_SUFFIX 2025-08-14T20:43:47.8306090Z  fleet_prefix = label 2025-08-14T20:43:47.8306631Z  else: 2025-08-14T20:43:47.8307260Z  prefixes.append(label) 2025-08-14T20:43:47.8307796Z  2025-08-14T20:43:47.8308183Z  if len(prefixes) > 1: 2025-08-14T20:43:47.8308679Z  log.error( 2025-08-14T20:43:47.8309805Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-14T20:43:47.8311076Z  ) 2025-08-14T20:43:47.8311523Z  prefixes = prefixes[:1] 2025-08-14T20:43:47.8312036Z  2025-08-14T20:43:47.8312437Z  # Fleet always comes first 2025-08-14T20:43:47.8312967Z  if fleet_prefix: 2025-08-14T20:43:47.8313482Z  prefixes.insert(0, fleet_prefix) 2025-08-14T20:43:47.8314035Z  2025-08-14T20:43:47.8314518Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:43:47.8315122Z  2025-08-14T20:43:47.8315488Z  2025-08-14T20:43:47.8316177Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:43:47.8316989Z  """ 2025-08-14T20:43:47.8317626Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:43:47.8318374Z  2025-08-14T20:43:47.8318987Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:43:47.8319737Z  """ 2025-08-14T20:43:47.8320275Z  gh = get_gh_client(github_token) 2025-08-14T20:43:47.8320886Z  issue = get_issue(gh, repo, issue_num) 2025-08-14T20:43:47.8321590Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:43:47.8322221Z  2025-08-14T20:43:47.8322585Z  2025-08-14T20:43:47.8323217Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:43:47.8324162Z  for _ in range(num_retries): 2025-08-14T20:43:47.8324685Z  try: 2025-08-14T20:43:47.8325176Z  req = Request(url=url, headers=headers) 2025-08-14T20:43:47.8325887Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:43:47.8326582Z  return json.loads(content) 2025-08-14T20:43:47.8327157Z  except Exception as e: 2025-08-14T20:43:47.8327767Z  log.warning(f"Could not download {url}: {e}") 2025-08-14T20:43:47.8328351Z  2025-08-14T20:43:47.8328962Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:43:47.8329724Z  return {} 2025-08-14T20:43:47.8330261Z  2025-08-14T20:43:47.8330627Z  2025-08-14T20:43:47.8330988Z @cache 2025-08-14T20:43:47.8331668Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:43:47.8332481Z  """ 2025-08-14T20:43:47.8332917Z  Dynamically get PR information 2025-08-14T20:43:47.8333450Z  """ 2025-08-14T20:43:47.8334005Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:43:47.8334675Z  headers = { 2025-08-14T20:43:47.8335202Z  "Accept": "application/vnd.github.v3+json", 2025-08-14T20:43:47.8335859Z  "Authorization": f"token {github_token}", 2025-08-14T20:43:47.8336513Z  } 2025-08-14T20:43:47.8337005Z  json_response: dict[str, Any] = download_json( 2025-08-14T20:43:47.8337665Z  url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:43:47.8338243Z  headers=headers, 2025-08-14T20:43:47.8338727Z  ) 2025-08-14T20:43:47.8339154Z  2025-08-14T20:43:47.8339593Z  if not json_response: 2025-08-14T20:43:47.8340354Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:43:47.8341158Z  return {} 2025-08-14T20:43:47.8341609Z  2025-08-14T20:43:47.8341992Z  return json_response 2025-08-14T20:43:47.8342475Z  2025-08-14T20:43:47.8342824Z  2025-08-14T20:43:47.8343455Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:43:47.8344237Z  """ 2025-08-14T20:43:47.8344820Z  Dynamically get the latest list of labels from the pull request 2025-08-14T20:43:47.8345507Z  """ 2025-08-14T20:43:47.8346042Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:43:47.8346694Z  return { 2025-08-14T20:43:47.8347341Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:43:47.8348077Z  } 2025-08-14T20:43:47.8348457Z  2025-08-14T20:43:47.8348817Z  2025-08-14T20:43:47.8349213Z def main() -> None: 2025-08-14T20:43:47.8349692Z  args = parse_args() 2025-08-14T20:43:47.8350279Z  2025-08-14T20:43:47.8350732Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:43:47.8351312Z  2025-08-14T20:43:47.8351714Z  # Check if the PR is opt-out 2025-08-14T20:43:47.8352254Z  if args.pr_number: 2025-08-14T20:43:47.8352981Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:43:47.8353804Z  if OPT_OUT_LABEL in labels: 2025-08-14T20:43:47.8354344Z  log.info( 2025-08-14T20:43:47.8355105Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:43:47.8355912Z  ) 2025-08-14T20:43:47.8356533Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.8357259Z  sys.exit() 2025-08-14T20:43:47.8357834Z  2025-08-14T20:43:47.8358211Z  try: 2025-08-14T20:43:47.8358708Z  rollout_state = get_rollout_state_from_issue( 2025-08-14T20:43:47.8359471Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:43:47.8360251Z  ) 2025-08-14T20:43:47.8360648Z  2025-08-14T20:43:47.8361083Z  username = get_potential_pr_author( 2025-08-14T20:43:47.8361660Z  args.github_token, 2025-08-14T20:43:47.8362192Z  args.github_repo, 2025-08-14T20:43:47.8362725Z  args.github_actor, 2025-08-14T20:43:47.8363263Z  args.github_ref_type, 2025-08-14T20:43:47.8363837Z  args.github_branch, 2025-08-14T20:43:47.8364344Z  ) 2025-08-14T20:43:47.8364742Z  2025-08-14T20:43:47.8365255Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:43:47.8365904Z  2025-08-14T20:43:47.8366346Z  runner_label_prefix = get_runner_prefix( 2025-08-14T20:43:47.8366943Z  rollout_state, 2025-08-14T20:43:47.8367495Z  (args.github_issue_owner, username), 2025-08-14T20:43:47.8368079Z  args.github_branch, 2025-08-14T20:43:47.8368645Z  args.eligible_experiments, 2025-08-14T20:43:47.8369228Z  args.opt_out_experiments, 2025-08-14T20:43:47.8369774Z  is_canary, 2025-08-14T20:43:47.8370340Z  ) 2025-08-14T20:43:47.8370741Z  2025-08-14T20:43:47.8371136Z  except Exception as e: 2025-08-14T20:43:47.8371639Z  log.error( 2025-08-14T20:43:47.8372388Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:43:47.8373168Z  ) 2025-08-14T20:43:47.8373698Z  2025-08-14T20:43:47.8374268Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.8374959Z  2025-08-14T20:43:47.8375320Z  2025-08-14T20:43:47.8375711Z if __name__ == "__main__": 2025-08-14T20:43:47.8376207Z  main() 2025-08-14T20:43:47.8376607Z  2025-08-14T20:43:47.8376980Z EOF 2025-08-14T20:43:47.8377357Z  2025-08-14T20:43:47.8377757Z cat runner_determinator.py 2025-08-14T20:43:47.8621358Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:47.8622151Z env: 2025-08-14T20:43:47.8622818Z GITHUB_TOKEN: *** 2025-08-14T20:43:47.8623234Z ISSUE_NUMBER: 5132 2025-08-14T20:43:47.8623726Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:47.8624227Z ISSUE_OWNER: 2025-08-14T20:43:47.8624625Z CHECK_EXPERIMENTS: 2025-08-14T20:43:47.8625047Z OPT_OUT_EXPERIMENTS: lf 2025-08-14T20:43:47.8625489Z PR_NUMBER: 2025-08-14T20:43:47.8625891Z ##[endgroup] 2025-08-14T20:43:47.8834810Z # flake8: noqa: G004 2025-08-14T20:43:47.8835153Z 2025-08-14T20:43:47.8835583Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:43:47.8836561Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:43:47.8837367Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:43:47.8837811Z 2025-08-14T20:43:47.8837963Z """ 2025-08-14T20:43:47.8838536Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:43:47.8839405Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:43:47.8840580Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:43:47.8841390Z of which runners should be used to run which job. 2025-08-14T20:43:47.8841789Z 2025-08-14T20:43:47.8842168Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:43:47.8843248Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:43:47.8844196Z settings are considered to be empty with only the second part, the user 2025-08-14T20:43:47.8844944Z list, defined. 2025-08-14T20:43:47.8845166Z 2025-08-14T20:43:47.8845528Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:43:47.8846448Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:43:47.8847252Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:43:47.8847699Z 2025-08-14T20:43:47.8848062Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:43:47.8848921Z The user list is also a comma separated list of additional features or 2025-08-14T20:43:47.8849646Z experiments which the user could be opted in to. 2025-08-14T20:43:47.8850264Z 2025-08-14T20:43:47.8850481Z The user list has the following rules: 2025-08-14T20:43:47.8850842Z 2025-08-14T20:43:47.8851166Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:43:47.8852061Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:43:47.8852812Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:43:47.8853196Z 2025-08-14T20:43:47.8853358Z Example config: 2025-08-14T20:43:47.8853804Z # A list of experiments that can be opted into. 2025-08-14T20:43:47.8854459Z # This defines the behavior they'll induce when opted into. 2025-08-14T20:43:47.8855091Z # Expected syntax is: 2025-08-14T20:43:47.8855724Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:43:47.8856689Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:43:47.8857291Z 2025-08-14T20:43:47.8857464Z experiments: 2025-08-14T20:43:47.8857843Z lf: 2025-08-14T20:43:47.8858214Z rollout_percent: 25 2025-08-14T20:43:47.8858683Z all_branches: false 2025-08-14T20:43:47.8859335Z default: true 2025-08-14T20:43:47.8859780Z --- 2025-08-14T20:43:47.8860180Z 2025-08-14T20:43:47.8860353Z # Opt-ins: 2025-08-14T20:43:47.8860923Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:43:47.8861776Z # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:43:47.8862550Z # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:43:47.8863203Z # Experiments should be from the above list. 2025-08-14T20:43:47.8863574Z 2025-08-14T20:43:47.8863754Z @User1,-lf,split_build 2025-08-14T20:43:47.8864181Z @User2,lf 2025-08-14T20:43:47.8864563Z @User3,split_build 2025-08-14T20:43:47.8864963Z """ 2025-08-14T20:43:47.8865200Z 2025-08-14T20:43:47.8865359Z import json 2025-08-14T20:43:47.8865725Z import logging 2025-08-14T20:43:47.8866098Z import os 2025-08-14T20:43:47.8866456Z import random 2025-08-14T20:43:47.8866821Z import re 2025-08-14T20:43:47.8867177Z import sys 2025-08-14T20:43:47.8867577Z from argparse import ArgumentParser 2025-08-14T20:43:47.8868108Z from collections.abc import Iterable 2025-08-14T20:43:47.8868626Z from functools import cache 2025-08-14T20:43:47.8869100Z from logging import LogRecord 2025-08-14T20:43:47.8869584Z from typing import Any, NamedTuple 2025-08-14T20:43:47.8870285Z from urllib.request import Request, urlopen 2025-08-14T20:43:47.8870664Z 2025-08-14T20:43:47.8870827Z import yaml 2025-08-14T20:43:47.8871223Z from github import Auth, Github 2025-08-14T20:43:47.8871706Z from github.Issue import Issue 2025-08-14T20:43:47.8872014Z 2025-08-14T20:43:47.8872021Z 2025-08-14T20:43:47.8872237Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:43:47.8872920Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:43:47.8873784Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:43:47.8874355Z 2025-08-14T20:43:47.8874587Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:43:47.8875290Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:43:47.8875819Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:43:47.8876366Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:43:47.8876720Z 2025-08-14T20:43:47.8876912Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:43:47.8877253Z 2025-08-14T20:43:47.8877442Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:43:47.8877895Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:43:47.8878175Z 2025-08-14T20:43:47.8878187Z 2025-08-14T20:43:47.8878376Z class Experiment(NamedTuple): 2025-08-14T20:43:47.8878842Z rollout_perc: float = ( 2025-08-14T20:43:47.8879472Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:43:47.8880346Z ) 2025-08-14T20:43:47.8880718Z all_branches: bool = ( 2025-08-14T20:43:47.8881343Z False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:43:47.8882007Z ) 2025-08-14T20:43:47.8882367Z default: bool = ( 2025-08-14T20:43:47.8882931Z True # If True, the experiment is enabled by default for all queries 2025-08-14T20:43:47.8883572Z ) 2025-08-14T20:43:47.8883764Z 2025-08-14T20:43:47.8883944Z # Add more fields as needed 2025-08-14T20:43:47.8884248Z 2025-08-14T20:43:47.8884254Z 2025-08-14T20:43:47.8884433Z class Settings(NamedTuple): 2025-08-14T20:43:47.8884866Z """ 2025-08-14T20:43:47.8885306Z Settings for the experiments that can be opted into. 2025-08-14T20:43:47.8885874Z """ 2025-08-14T20:43:47.8886063Z 2025-08-14T20:43:47.8886262Z experiments: dict[str, Experiment] = {} 2025-08-14T20:43:47.8886617Z 2025-08-14T20:43:47.8886629Z 2025-08-14T20:43:47.8886832Z class ColorFormatter(logging.Formatter): 2025-08-14T20:43:47.8887463Z """Color codes the log messages based on the log level""" 2025-08-14T20:43:47.8887891Z 2025-08-14T20:43:47.8888048Z COLORS = { 2025-08-14T20:43:47.8888440Z "WARNING": "\033[33m", # Yellow 2025-08-14T20:43:47.8889105Z "ERROR": "\033[31m", # Red 2025-08-14T20:43:47.8889640Z "CRITICAL": "\033[31m", # Red 2025-08-14T20:43:47.8890395Z "INFO": "\033[0m", # Reset 2025-08-14T20:43:47.8890884Z "DEBUG": "\033[0m", # Reset 2025-08-14T20:43:47.8891348Z } 2025-08-14T20:43:47.8891549Z 2025-08-14T20:43:47.8891764Z def format(self, record: LogRecord) -> str: 2025-08-14T20:43:47.8892499Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:43:47.8893320Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:43:47.8893900Z return super().format(record) 2025-08-14T20:43:47.8894230Z 2025-08-14T20:43:47.8894237Z 2025-08-14T20:43:47.8894427Z handler = logging.StreamHandler() 2025-08-14T20:43:47.8895117Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:43:47.8895666Z 2025-08-14T20:43:47.8895908Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:43:47.8896488Z log.addHandler(handler) 2025-08-14T20:43:47.8896934Z log.setLevel(logging.INFO) 2025-08-14T20:43:47.8897398Z 2025-08-14T20:43:47.8897408Z 2025-08-14T20:43:47.8897725Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:43:47.8898290Z """ 2025-08-14T20:43:47.8898782Z Defines outputs of the github action that invokes this script 2025-08-14T20:43:47.8899401Z """ 2025-08-14T20:43:47.8899764Z if not GITHUB_OUTPUT: 2025-08-14T20:43:47.8901026Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:43:47.8902131Z log.warning( 2025-08-14T20:43:47.8902971Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:43:47.8903882Z ) 2025-08-14T20:43:47.8913943Z print(f"::set-output name={key}::{value}") 2025-08-14T20:43:47.8914558Z return 2025-08-14T20:43:47.8914796Z 2025-08-14T20:43:47.8915197Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:43:47.8915816Z log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:43:47.8916389Z f.write(f"{key}={value}\n") 2025-08-14T20:43:47.8916718Z 2025-08-14T20:43:47.8916725Z 2025-08-14T20:43:47.8917026Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:43:47.8917662Z return frozenset( 2025-08-14T20:43:47.8918316Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:43:47.8918998Z ) 2025-08-14T20:43:47.8919195Z 2025-08-14T20:43:47.8919202Z 2025-08-14T20:43:47.8919383Z def parse_args() -> Any: 2025-08-14T20:43:47.8920131Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:43:47.8921056Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:43:47.8921834Z parser.add_argument( 2025-08-14T20:43:47.8922289Z "--github-issue-repo", 2025-08-14T20:43:47.8922740Z type=str, 2025-08-14T20:43:47.8923152Z required=False, 2025-08-14T20:43:47.8923599Z default="pytorch/test-infra", 2025-08-14T20:43:47.8924126Z help="GitHub repo to get the issue", 2025-08-14T20:43:47.8924630Z ) 2025-08-14T20:43:47.8925009Z parser.add_argument( 2025-08-14T20:43:47.8925437Z "--github-repo", 2025-08-14T20:43:47.8925860Z type=str, 2025-08-14T20:43:47.8926240Z required=True, 2025-08-14T20:43:47.8926704Z help="GitHub repo where CI is running", 2025-08-14T20:43:47.8927227Z ) 2025-08-14T20:43:47.8927593Z parser.add_argument( 2025-08-14T20:43:47.8928194Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:43:47.8928850Z ) 2025-08-14T20:43:47.8929210Z parser.add_argument( 2025-08-14T20:43:47.8929817Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:43:47.8930693Z ) 2025-08-14T20:43:47.8931059Z parser.add_argument( 2025-08-14T20:43:47.8931847Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:43:47.8932524Z ) 2025-08-14T20:43:47.8932895Z parser.add_argument( 2025-08-14T20:43:47.8933535Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:43:47.8934244Z ) 2025-08-14T20:43:47.8934612Z parser.add_argument( 2025-08-14T20:43:47.8935050Z "--github-ref-type", 2025-08-14T20:43:47.8935506Z type=str, 2025-08-14T20:43:47.8935893Z required=True, 2025-08-14T20:43:47.8936375Z help="Current GitHub ref type, branch or tag", 2025-08-14T20:43:47.8936923Z ) 2025-08-14T20:43:47.8985140Z parser.add_argument( 2025-08-14T20:43:47.8985705Z "--eligible-experiments", 2025-08-14T20:43:47.8986263Z type=_str_comma_separated_to_set, 2025-08-14T20:43:47.8986781Z required=False, 2025-08-14T20:43:47.8987196Z default="", 2025-08-14T20:43:47.8988054Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:43:47.8988998Z ) 2025-08-14T20:43:47.8989364Z parser.add_argument( 2025-08-14T20:43:47.8989837Z "--opt-out-experiments", 2025-08-14T20:43:47.8990478Z type=_str_comma_separated_to_set, 2025-08-14T20:43:47.8990988Z required=False, 2025-08-14T20:43:47.8991401Z default="", 2025-08-14T20:43:47.8991781Z help=( 2025-08-14T20:43:47.8992443Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:43:47.8993558Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:43:47.8994378Z ), 2025-08-14T20:43:47.8994727Z ) 2025-08-14T20:43:47.8995100Z parser.add_argument( 2025-08-14T20:43:47.8995535Z "--pr-number", 2025-08-14T20:43:47.8995939Z type=str, 2025-08-14T20:43:47.8996333Z required=False, 2025-08-14T20:43:47.8996742Z default="", 2025-08-14T20:43:47.8997374Z help="the optional PR number where this is run", 2025-08-14T20:43:47.8997937Z ) 2025-08-14T20:43:47.8998135Z 2025-08-14T20:43:47.8998323Z return parser.parse_args() 2025-08-14T20:43:47.8998628Z 2025-08-14T20:43:47.8998635Z 2025-08-14T20:43:47.8999032Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.8999773Z auth = Auth.Token(github_token) 2025-08-14T20:43:47.9000381Z return Github(auth=auth) 2025-08-14T20:43:47.9000669Z 2025-08-14T20:43:47.9000675Z 2025-08-14T20:43:47.9001117Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:43:47.9001905Z repo = gh.get_repo(repo) 2025-08-14T20:43:47.9002392Z return repo.get_issue(number=issue_num) 2025-08-14T20:43:47.9002755Z 2025-08-14T20:43:47.9002761Z 2025-08-14T20:43:47.9002945Z def get_potential_pr_author( 2025-08-14T20:43:47.9003585Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:43:47.9004248Z ) -> str: 2025-08-14T20:43:47.9004754Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:43:47.9005544Z # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:43:47.9006466Z # embedded in the tag name: ciflow// 2025-08-14T20:43:47.9006885Z 2025-08-14T20:43:47.9007075Z gh = get_gh_client(github_token) 2025-08-14T20:43:47.9007404Z 2025-08-14T20:43:47.9007664Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:43:47.9008277Z split_tag = ref_name.split("/") 2025-08-14T20:43:47.9008761Z if ( 2025-08-14T20:43:47.9009142Z len(split_tag) == 3 2025-08-14T20:43:47.9009605Z and split_tag[0] == "ciflow" 2025-08-14T20:43:47.9010237Z and split_tag[2].isnumeric() 2025-08-14T20:43:47.9010719Z ): 2025-08-14T20:43:47.9011096Z pr_number = split_tag[2] 2025-08-14T20:43:47.9011763Z try: 2025-08-14T20:43:47.9012214Z repository = gh.get_repo(repo) 2025-08-14T20:43:47.9012839Z pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:43:47.9013428Z except Exception as e: 2025-08-14T20:43:47.9013937Z raise Exception( # noqa: TRY002 2025-08-14T20:43:47.9014591Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:43:47.9015230Z ) from e 2025-08-14T20:43:47.9015751Z return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:43:47.9016433Z # In all other cases, return the original input username 2025-08-14T20:43:47.9017007Z return username 2025-08-14T20:43:47.9017241Z 2025-08-14T20:43:47.9017247Z 2025-08-14T20:43:47.9017467Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:43:47.9017991Z """ 2025-08-14T20:43:47.9018643Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:43:47.9019408Z """ 2025-08-14T20:43:47.9020095Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:43:47.9020619Z 2025-08-14T20:43:47.9020626Z 2025-08-14T20:43:47.9020818Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:43:47.9021295Z try: 2025-08-14T20:43:47.9021665Z data = yaml.safe_load(yaml_text) 2025-08-14T20:43:47.9022168Z return data 2025-08-14T20:43:47.9022571Z except yaml.YAMLError: 2025-08-14T20:43:47.9023043Z log.exception("Error loading YAML") 2025-08-14T20:43:47.9023536Z raise 2025-08-14T20:43:47.9023750Z 2025-08-14T20:43:47.9023757Z 2025-08-14T20:43:47.9024161Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:43:47.9024910Z """ 2025-08-14T20:43:47.9025524Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:43:47.9026118Z 2025-08-14T20:43:47.9026595Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.9027350Z and the text below is the list of opted in users. 2025-08-14T20:43:47.9027751Z 2025-08-14T20:43:47.9028120Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:43:47.9028825Z """ 2025-08-14T20:43:47.9029304Z rollout_state_parts = rollout_state.split("---") 2025-08-14T20:43:47.9029898Z if len(rollout_state_parts) >= 2: 2025-08-14T20:43:47.9030607Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:43:47.9031188Z else: 2025-08-14T20:43:47.9031556Z return "", rollout_state 2025-08-14T20:43:47.9031853Z 2025-08-14T20:43:47.9031861Z 2025-08-14T20:43:47.9032059Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:43:47.9032544Z """ 2025-08-14T20:43:47.9033052Z Dictionary of users with a list of features they have opted into 2025-08-14T20:43:47.9033682Z """ 2025-08-14T20:43:47.9033882Z 2025-08-14T20:43:47.9033894Z 2025-08-14T20:43:47.9034230Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:43:47.9034874Z """ 2025-08-14T20:43:47.9035563Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:43:47.9036229Z 2025-08-14T20:43:47.9036843Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:43:47.9037812Z - Example line: "@User1,lf,split_build" 2025-08-14T20:43:47.9038480Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:43:47.9038951Z 2025-08-14T20:43:47.9038958Z 2025-08-14T20:43:47.9039117Z """ 2025-08-14T20:43:47.9039479Z optins = UserOptins() 2025-08-14T20:43:47.9040062Z for user in user_optin_text.split("\n"): 2025-08-14T20:43:47.9040609Z user = user.strip("\r\n\t -") 2025-08-14T20:43:47.9041134Z if not user or not user.startswith("@"): 2025-08-14T20:43:47.9041818Z # Not a valid user. Skip 2025-08-14T20:43:47.9042309Z continue 2025-08-14T20:43:47.9042545Z 2025-08-14T20:43:47.9042699Z if user: 2025-08-14T20:43:47.9043132Z usr_name = user.split(",")[0].strip("@") 2025-08-14T20:43:47.9043813Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:43:47.9044298Z 2025-08-14T20:43:47.9044461Z return optins 2025-08-14T20:43:47.9044691Z 2025-08-14T20:43:47.9044702Z 2025-08-14T20:43:47.9044986Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:43:47.9045573Z """ 2025-08-14T20:43:47.9045953Z Check if the experiment name is valid. 2025-08-14T20:43:47.9046456Z A valid name: 2025-08-14T20:43:47.9047074Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:43:47.9047989Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:43:47.9048717Z - Cannot contain spaces 2025-08-14T20:43:47.9049174Z """ 2025-08-14T20:43:47.9049366Z 2025-08-14T20:43:47.9049624Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:43:47.9050423Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:43:47.9050864Z 2025-08-14T20:43:47.9051018Z if valid: 2025-08-14T20:43:47.9051391Z return True 2025-08-14T20:43:47.9051621Z 2025-08-14T20:43:47.9051785Z log.error( 2025-08-14T20:43:47.9053227Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-14T20:43:47.9054769Z ) 2025-08-14T20:43:47.9055114Z return False 2025-08-14T20:43:47.9055350Z 2025-08-14T20:43:47.9055357Z 2025-08-14T20:43:47.9055654Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:43:47.9056256Z """ 2025-08-14T20:43:47.9056960Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:43:47.9057671Z """ 2025-08-14T20:43:47.9058009Z try: 2025-08-14T20:43:47.9058379Z if settings_text: 2025-08-14T20:43:47.9059094Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:43:47.9059883Z # for easy reading 2025-08-14T20:43:47.9060781Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:43:47.9061659Z # the backtick character in shell commands. 2025-08-14T20:43:47.9062257Z backtick = chr(96) # backtick character 2025-08-14T20:43:47.9062903Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:43:47.9063556Z settings = load_yaml(settings_text) 2025-08-14T20:43:47.9063919Z 2025-08-14T20:43:47.9064333Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:43:47.9065095Z experiments = {} 2025-08-14T20:43:47.9065385Z 2025-08-14T20:43:47.9065763Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:43:47.9066523Z if not is_valid_experiment_name(exp_name): 2025-08-14T20:43:47.9067634Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-14T20:43:47.9068680Z continue 2025-08-14T20:43:47.9068965Z 2025-08-14T20:43:47.9069146Z valid_settings = {} 2025-08-14T20:43:47.9069651Z for setting in exp_settings: 2025-08-14T20:43:47.9070520Z if setting not in Experiment._fields: 2025-08-14T20:43:47.9071068Z log.warning( 2025-08-14T20:43:47.9071760Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:43:47.9072603Z ) 2025-08-14T20:43:47.9073024Z else: 2025-08-14T20:43:47.9073530Z valid_settings[setting] = exp_settings[setting] 2025-08-14T20:43:47.9073952Z 2025-08-14T20:43:47.9074220Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:43:47.9074846Z return Settings(experiments) 2025-08-14T20:43:47.9075201Z 2025-08-14T20:43:47.9075371Z except Exception: 2025-08-14T20:43:47.9075842Z log.exception("Failed to parse settings") 2025-08-14T20:43:47.9076224Z 2025-08-14T20:43:47.9076395Z return Settings() 2025-08-14T20:43:47.9076640Z 2025-08-14T20:43:47.9076647Z 2025-08-14T20:43:47.9076894Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:43:47.9077455Z """ 2025-08-14T20:43:47.9077875Z Parse settings, if any, from the rollout state. 2025-08-14T20:43:47.9078287Z 2025-08-14T20:43:47.9078636Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:47.9079401Z and the text below is the list of opted in users. 2025-08-14T20:43:47.9079802Z 2025-08-14T20:43:47.9080392Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:43:47.9081149Z """ 2025-08-14T20:43:47.9081686Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.9082439Z return parse_settings_from_text(settings_text) 2025-08-14T20:43:47.9082832Z 2025-08-14T20:43:47.9082839Z 2025-08-14T20:43:47.9083079Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:43:47.9083629Z """ 2025-08-14T20:43:47.9084000Z Parse users from the rollout state. 2025-08-14T20:43:47.9084345Z 2025-08-14T20:43:47.9084497Z """ 2025-08-14T20:43:47.9085010Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:47.9085730Z return parse_user_opt_in_from_text(users_text) 2025-08-14T20:43:47.9086136Z 2025-08-14T20:43:47.9086150Z 2025-08-14T20:43:47.9086680Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.9087430Z """ 2025-08-14T20:43:47.9087842Z Check if a user is opted into an experiment 2025-08-14T20:43:47.9088377Z """ 2025-08-14T20:43:47.9088814Z return experiment_name in user_optins.get(user, []) 2025-08-14T20:43:47.9089223Z 2025-08-14T20:43:47.9089230Z 2025-08-14T20:43:47.9089645Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:47.9090597Z """ 2025-08-14T20:43:47.9091052Z Check if a user explicitly opted out of an experiment 2025-08-14T20:43:47.9091618Z """ 2025-08-14T20:43:47.9092110Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:43:47.9092779Z experiment_optout = "-" + experiment_name 2025-08-14T20:43:47.9093400Z if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:43:47.9093979Z return False 2025-08-14T20:43:47.9094237Z 2025-08-14T20:43:47.9094512Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:43:47.9095105Z log.warning( 2025-08-14T20:43:47.9095897Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:43:47.9096765Z ) 2025-08-14T20:43:47.9096967Z 2025-08-14T20:43:47.9097129Z return True 2025-08-14T20:43:47.9097359Z 2025-08-14T20:43:47.9097365Z 2025-08-14T20:43:47.9097540Z def get_runner_prefix( 2025-08-14T20:43:47.9097963Z rollout_state: str, 2025-08-14T20:43:47.9098415Z workflow_requestors: Iterable[str], 2025-08-14T20:43:47.9098923Z branch: str, 2025-08-14T20:43:47.9099399Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.9100165Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:47.9100747Z is_canary: bool = False, 2025-08-14T20:43:47.9101194Z ) -> str: 2025-08-14T20:43:47.9101594Z settings = parse_settings(rollout_state) 2025-08-14T20:43:47.9102314Z user_optins = parse_users(rollout_state) 2025-08-14T20:43:47.9102672Z 2025-08-14T20:43:47.9102849Z fleet_prefix = "" 2025-08-14T20:43:47.9103257Z prefixes = [] 2025-08-14T20:43:47.9103875Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:43:47.9104796Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:43:47.9105510Z log.info( 2025-08-14T20:43:47.9106180Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:43:47.9106932Z ) 2025-08-14T20:43:47.9107311Z continue 2025-08-14T20:43:47.9107555Z 2025-08-14T20:43:47.9107739Z if opt_out_experiments: 2025-08-14T20:43:47.9108259Z if experiment_name in opt_out_experiments: 2025-08-14T20:43:47.9108886Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:43:47.9109474Z log.info( 2025-08-14T20:43:47.9110492Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:43:47.9111476Z ) 2025-08-14T20:43:47.9111877Z continue 2025-08-14T20:43:47.9112143Z 2025-08-14T20:43:47.9112330Z if eligible_experiments: 2025-08-14T20:43:47.9112875Z if experiment_name not in eligible_experiments: 2025-08-14T20:43:47.9113492Z exp_list = ", ".join(eligible_experiments) 2025-08-14T20:43:47.9114033Z log.info( 2025-08-14T20:43:47.9114792Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:43:47.9115620Z ) 2025-08-14T20:43:47.9116012Z continue 2025-08-14T20:43:47.9116470Z elif not experiment_settings.default: 2025-08-14T20:43:47.9116993Z log.info( 2025-08-14T20:43:47.9117760Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:43:47.9118535Z ) 2025-08-14T20:43:47.9118899Z continue 2025-08-14T20:43:47.9118908Z 2025-08-14T20:43:47.9119185Z # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:43:47.9119363Z opted_out_users = [ 2025-08-14T20:43:47.9119522Z requestor 2025-08-14T20:43:47.9119723Z for requestor in workflow_requestors 2025-08-14T20:43:47.9120181Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.9120344Z ] 2025-08-14T20:43:47.9120353Z 2025-08-14T20:43:47.9120531Z if opted_out_users: 2025-08-14T20:43:47.9120697Z log.info( 2025-08-14T20:43:47.9121066Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:43:47.9121222Z ) 2025-08-14T20:43:47.9121384Z continue 2025-08-14T20:43:47.9121397Z 2025-08-14T20:43:47.9121668Z # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:43:47.9121842Z opted_in_users = [ 2025-08-14T20:43:47.9122000Z requestor 2025-08-14T20:43:47.9122207Z for requestor in workflow_requestors 2025-08-14T20:43:47.9122499Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:43:47.9122655Z ] 2025-08-14T20:43:47.9122663Z 2025-08-14T20:43:47.9122834Z enabled = False 2025-08-14T20:43:47.9123010Z if opted_in_users: 2025-08-14T20:43:47.9123170Z log.info( 2025-08-14T20:43:47.9123520Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:43:47.9123677Z ) 2025-08-14T20:43:47.9123846Z enabled = True 2025-08-14T20:43:47.9123854Z 2025-08-14T20:43:47.9124069Z elif experiment_settings.rollout_perc: 2025-08-14T20:43:47.9124522Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:43:47.9124964Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:43:47.9125135Z log.info( 2025-08-14T20:43:47.9125727Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:43:47.9125884Z ) 2025-08-14T20:43:47.9126057Z enabled = True 2025-08-14T20:43:47.9126065Z 2025-08-14T20:43:47.9126228Z if enabled: 2025-08-14T20:43:47.9126410Z label = experiment_name 2025-08-14T20:43:47.9126627Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:43:47.9127053Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:43:47.9127320Z # - If it's enabled, then we always list it's prefix first 2025-08-14T20:43:47.9127625Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:43:47.9127800Z if is_canary: 2025-08-14T20:43:47.9127997Z label += CANARY_FLEET_SUFFIX 2025-08-14T20:43:47.9128177Z fleet_prefix = label 2025-08-14T20:43:47.9128332Z else: 2025-08-14T20:43:47.9128523Z prefixes.append(label) 2025-08-14T20:43:47.9128531Z 2025-08-14T20:43:47.9128703Z if len(prefixes) > 1: 2025-08-14T20:43:47.9128869Z log.error( 2025-08-14T20:43:47.9129680Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-14T20:43:47.9129837Z ) 2025-08-14T20:43:47.9130125Z prefixes = prefixes[:1] 2025-08-14T20:43:47.9130134Z 2025-08-14T20:43:47.9130319Z # Fleet always comes first 2025-08-14T20:43:47.9130493Z if fleet_prefix: 2025-08-14T20:43:47.9130690Z prefixes.insert(0, fleet_prefix) 2025-08-14T20:43:47.9130703Z 2025-08-14T20:43:47.9131065Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:43:47.9131075Z 2025-08-14T20:43:47.9131081Z 2025-08-14T20:43:47.9131535Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:43:47.9131694Z """ 2025-08-14T20:43:47.9132081Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:43:47.9132089Z 2025-08-14T20:43:47.9132476Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:43:47.9132633Z """ 2025-08-14T20:43:47.9132822Z gh = get_gh_client(github_token) 2025-08-14T20:43:47.9133026Z issue = get_issue(gh, repo, issue_num) 2025-08-14T20:43:47.9133292Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:43:47.9133301Z 2025-08-14T20:43:47.9133307Z 2025-08-14T20:43:47.9133700Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:43:47.9133990Z for _ in range(num_retries): 2025-08-14T20:43:47.9134159Z try: 2025-08-14T20:43:47.9134369Z req = Request(url=url, headers=headers) 2025-08-14T20:43:47.9134651Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:43:47.9134842Z return json.loads(content) 2025-08-14T20:43:47.9135025Z except Exception as e: 2025-08-14T20:43:47.9135256Z log.warning(f"Could not download {url}: {e}") 2025-08-14T20:43:47.9135264Z 2025-08-14T20:43:47.9135647Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:43:47.9135808Z return {} 2025-08-14T20:43:47.9135816Z 2025-08-14T20:43:47.9135823Z 2025-08-14T20:43:47.9135976Z @cache 2025-08-14T20:43:47.9136398Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:43:47.9136552Z """ 2025-08-14T20:43:47.9136744Z Dynamically get PR information 2025-08-14T20:43:47.9136899Z """ 2025-08-14T20:43:47.9137335Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:43:47.9137495Z headers = { 2025-08-14T20:43:47.9137718Z "Accept": "application/vnd.github.v3+json", 2025-08-14T20:43:47.9137930Z "Authorization": f"token {github_token}", 2025-08-14T20:43:47.9138082Z } 2025-08-14T20:43:47.9138304Z json_response: dict[str, Any] = download_json( 2025-08-14T20:43:47.9138509Z url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:43:47.9138683Z headers=headers, 2025-08-14T20:43:47.9138836Z ) 2025-08-14T20:43:47.9138844Z 2025-08-14T20:43:47.9139017Z if not json_response: 2025-08-14T20:43:47.9139300Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:43:47.9139459Z return {} 2025-08-14T20:43:47.9139467Z 2025-08-14T20:43:47.9139639Z return json_response 2025-08-14T20:43:47.9139646Z 2025-08-14T20:43:47.9139652Z 2025-08-14T20:43:47.9140155Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:43:47.9140327Z """ 2025-08-14T20:43:47.9140648Z Dynamically get the latest list of labels from the pull request 2025-08-14T20:43:47.9140809Z """ 2025-08-14T20:43:47.9141084Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:43:47.9141244Z return { 2025-08-14T20:43:47.9141597Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:43:47.9141753Z } 2025-08-14T20:43:47.9141761Z 2025-08-14T20:43:47.9141767Z 2025-08-14T20:43:47.9141934Z def main() -> None: 2025-08-14T20:43:47.9142110Z args = parse_args() 2025-08-14T20:43:47.9142117Z 2025-08-14T20:43:47.9142341Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:43:47.9142348Z 2025-08-14T20:43:47.9142536Z # Check if the PR is opt-out 2025-08-14T20:43:47.9142704Z if args.pr_number: 2025-08-14T20:43:47.9143089Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:43:47.9143394Z if OPT_OUT_LABEL in labels: 2025-08-14T20:43:47.9143562Z log.info( 2025-08-14T20:43:47.9143993Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:43:47.9144150Z ) 2025-08-14T20:43:47.9144478Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.9144640Z sys.exit() 2025-08-14T20:43:47.9144647Z 2025-08-14T20:43:47.9144913Z try: 2025-08-14T20:43:47.9145326Z rollout_state = get_rollout_state_from_issue( 2025-08-14T20:43:47.9146637Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:43:47.9147851Z ) 2025-08-14T20:43:47.9148267Z 2025-08-14T20:43:47.9148664Z username = get_potential_pr_author( 2025-08-14T20:43:47.9149790Z args.github_token, 2025-08-14T20:43:47.9150893Z args.github_repo, 2025-08-14T20:43:47.9151753Z args.github_actor, 2025-08-14T20:43:47.9152640Z args.github_ref_type, 2025-08-14T20:43:47.9153480Z args.github_branch, 2025-08-14T20:43:47.9154215Z ) 2025-08-14T20:43:47.9154574Z 2025-08-14T20:43:47.9155130Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:43:47.9156045Z 2025-08-14T20:43:47.9156459Z runner_label_prefix = get_runner_prefix( 2025-08-14T20:43:47.9157488Z rollout_state, 2025-08-14T20:43:47.9158345Z (args.github_issue_owner, username), 2025-08-14T20:43:47.9159360Z args.github_branch, 2025-08-14T20:43:47.9160531Z args.eligible_experiments, 2025-08-14T20:43:47.9161586Z args.opt_out_experiments, 2025-08-14T20:43:47.9162493Z is_canary, 2025-08-14T20:43:47.9163226Z ) 2025-08-14T20:43:47.9163580Z 2025-08-14T20:43:47.9163920Z except Exception as e: 2025-08-14T20:43:47.9164772Z log.error( 2025-08-14T20:43:47.9166046Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:43:47.9167692Z ) 2025-08-14T20:43:47.9168069Z 2025-08-14T20:43:47.9168658Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:47.9169639Z 2025-08-14T20:43:47.9169651Z 2025-08-14T20:43:47.9170183Z if __name__ == "__main__": 2025-08-14T20:43:47.9171026Z main() 2025-08-14T20:43:47.9171401Z 2025-08-14T20:43:47.9262905Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:43:47.9263794Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:43:47.9302603Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:47.9303070Z env: 2025-08-14T20:43:47.9303645Z GITHUB_TOKEN: *** 2025-08-14T20:43:47.9304052Z ISSUE_NUMBER: 5132 2025-08-14T20:43:47.9304482Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:47.9304981Z ISSUE_OWNER: 2025-08-14T20:43:47.9305366Z CHECK_EXPERIMENTS: 2025-08-14T20:43:47.9305801Z OPT_OUT_EXPERIMENTS: lf 2025-08-14T20:43:47.9306223Z PR_NUMBER: 2025-08-14T20:43:47.9306599Z ##[endgroup] 2025-08-14T20:43:48.9263322Z Defaulting to user installation because normal site-packages is not writeable 2025-08-14T20:43:49.8675075Z Collecting urllib3==1.26.18 2025-08-14T20:43:49.9118568Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-14T20:43:49.9341333Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.4 MB/s eta 0:00:00 2025-08-14T20:43:49.9591755Z Collecting PyGithub==2.3.0 2025-08-14T20:43:49.9675374Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-14T20:43:50.0116571Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-14T20:43:50.0187408Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl.metadata (8.6 kB) 2025-08-14T20:43:50.0228489Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-14T20:43:50.0245370Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-08-14T20:43:50.0260503Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-14T20:43:50.0547186Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-14T20:43:50.0619391Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-14T20:43:50.0861593Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-08-14T20:43:50.2067263Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:43:50.2171114Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-14T20:43:50.3319327Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-14T20:43:50.3399178Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-08-14T20:43:50.3623873Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:43:50.3695096Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-14T20:43:50.3954184Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-14T20:43:50.4080745Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 12.3 MB/s eta 0:00:00 2025-08-14T20:43:50.4152911Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-14T20:43:50.4411640Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 14.4 MB/s eta 0:00:00 2025-08-14T20:43:50.4485824Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-08-14T20:43:50.4930277Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 19.8 MB/s eta 0:00:00 2025-08-14T20:43:50.5003602Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-14T20:43:50.5097915Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-14T20:43:50.5309584Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 24.1 MB/s eta 0:00:00 2025-08-14T20:43:50.5400215Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-08-14T20:43:50.5447187Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 24.9 MB/s eta 0:00:00 2025-08-14T20:43:50.5522400Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-14T20:43:50.5571637Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 32.1 MB/s eta 0:00:00 2025-08-14T20:43:50.8482028Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-14T20:43:51.3695218Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-08-14T20:43:51.4472173Z ##[group]Run curr_branch="main" 2025-08-14T20:43:51.4472483Z curr_branch="main" 2025-08-14T20:43:51.4472709Z curr_ref_type="branch" 2025-08-14T20:43:51.4472986Z echo "Current branch is '$curr_branch'" 2025-08-14T20:43:51.4473244Z  2025-08-14T20:43:51.4473435Z python3 runner_determinator.py \ 2025-08-14T20:43:51.4473725Z  --github-token "$GITHUB_TOKEN" \ 2025-08-14T20:43:51.4474000Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-14T20:43:51.4474262Z  --github-branch "$curr_branch" \ 2025-08-14T20:43:51.4474531Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-14T20:43:51.4474811Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-14T20:43:51.4475092Z  --github-ref-type "$curr_ref_type" \ 2025-08-14T20:43:51.4475394Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-14T20:43:51.4475706Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-14T20:43:51.4476072Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-14T20:43:51.4476360Z  --pr-number "${PR_NUMBER}" 2025-08-14T20:43:51.4518120Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:51.4518351Z env: 2025-08-14T20:43:51.4518899Z GITHUB_TOKEN: *** 2025-08-14T20:43:51.4519094Z ISSUE_NUMBER: 5132 2025-08-14T20:43:51.4519305Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:51.4519537Z ISSUE_OWNER: 2025-08-14T20:43:51.4519723Z CHECK_EXPERIMENTS: 2025-08-14T20:43:51.4519917Z OPT_OUT_EXPERIMENTS: lf 2025-08-14T20:43:51.4520333Z PR_NUMBER: 2025-08-14T20:43:51.4520494Z ##[endgroup] 2025-08-14T20:43:51.4577926Z Current branch is 'main' 2025-08-14T20:43:52.8993297Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-08-14T20:43:52.8994566Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-14T20:43:52.8995628Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-14T20:43:52.8996413Z INFO : Setting output: label-type='' 2025-08-14T20:43:52.9295024Z Evaluate and set job outputs 2025-08-14T20:43:52.9301913Z Cleaning up orphan processes