2025-09-07T06:09:28.7414924Z Current runner version: '2.328.0' 2025-09-07T06:09:28.7438694Z ##[group]Runner Image Provisioner 2025-09-07T06:09:28.7439576Z Hosted Compute Agent 2025-09-07T06:09:28.7440133Z Version: 20250829.383 2025-09-07T06:09:28.7440746Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T06:09:28.7441427Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T06:09:28.7442107Z ##[endgroup] 2025-09-07T06:09:28.7442606Z ##[group]Operating System 2025-09-07T06:09:28.7443149Z Ubuntu 2025-09-07T06:09:28.7443677Z 24.04.3 2025-09-07T06:09:28.7444169Z LTS 2025-09-07T06:09:28.7444635Z ##[endgroup] 2025-09-07T06:09:28.7445192Z ##[group]Runner Image 2025-09-07T06:09:28.7445749Z Image: ubuntu-24.04 2025-09-07T06:09:28.7446423Z Version: 20250831.1.0 2025-09-07T06:09:28.7447504Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T06:09:28.7449048Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T06:09:28.7450059Z ##[endgroup] 2025-09-07T06:09:28.7451121Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T06:09:28.7453283Z Contents: read 2025-09-07T06:09:28.7453876Z Metadata: read 2025-09-07T06:09:28.7454357Z Packages: read 2025-09-07T06:09:28.7454893Z ##[endgroup] 2025-09-07T06:09:28.7457171Z Secret source: Actions 2025-09-07T06:09:28.7457883Z Prepare workflow directory 2025-09-07T06:09:28.7984157Z Prepare all required actions 2025-09-07T06:09:28.8043487Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T06:09:28.8048749Z ##[group] Inputs 2025-09-07T06:09:28.8049398Z check_experiments: 2025-09-07T06:09:28.8049932Z opt_out_experiments: 2025-09-07T06:09:28.8050597Z triggering_actor: pytorchmergebot 2025-09-07T06:09:28.8051260Z issue_owner: 2025-09-07T06:09:28.8051777Z curr_branch: main 2025-09-07T06:09:28.8052464Z curr_ref_type: branch 2025-09-07T06:09:28.8053016Z issue_number: 5132 2025-09-07T06:09:28.8053610Z ##[endgroup] 2025-09-07T06:09:28.8054174Z Complete job name: get-label-type / runner-determinator 2025-09-07T06:09:28.8653337Z ##[group]Run cat < runner_determinator.py 2025-09-07T06:09:28.8655999Z cat < runner_determinator.py 2025-09-07T06:09:28.8656701Z # flake8: noqa: G004 2025-09-07T06:09:28.8657237Z  2025-09-07T06:09:28.8658015Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:28.8659093Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:28.8659963Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:28.8660707Z  2025-09-07T06:09:28.8661162Z """ 2025-09-07T06:09:28.8661831Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:28.8662865Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:28.8664011Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:28.8664993Z of which runners should be used to run which job. 2025-09-07T06:09:28.8665625Z  2025-09-07T06:09:28.8666490Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:28.8667556Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:28.8668524Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:28.8669366Z list, defined. 2025-09-07T06:09:28.8669830Z  2025-09-07T06:09:28.8670573Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:28.8671602Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:28.8672549Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:28.8673610Z  2025-09-07T06:09:28.8674268Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:28.8675236Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:28.8676529Z experiments which the user could be opted in to. 2025-09-07T06:09:28.8677227Z  2025-09-07T06:09:28.8677704Z The user list has the following rules: 2025-09-07T06:09:28.8678389Z  2025-09-07T06:09:28.8679017Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:28.8679931Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:28.8680867Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:28.8681475Z  2025-09-07T06:09:28.8682051Z Example config: 2025-09-07T06:09:28.8683135Z  # A list of experiments that can be opted into. 2025-09-07T06:09:28.8684463Z  # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:28.8685638Z  # Expected syntax is: 2025-09-07T06:09:28.8687053Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:28.8688316Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:28.8689192Z  2025-09-07T06:09:28.8689663Z  experiments: 2025-09-07T06:09:28.8690237Z  lf: 2025-09-07T06:09:28.8690706Z  rollout_percent: 25 2025-09-07T06:09:28.8691348Z  all_branches: false 2025-09-07T06:09:28.8691911Z  default: true 2025-09-07T06:09:28.8692447Z  --- 2025-09-07T06:09:28.8692972Z  2025-09-07T06:09:28.8693399Z  # Opt-ins: 2025-09-07T06:09:28.8694119Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:28.8695298Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:28.8696743Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:28.8697889Z  # Experiments should be from the above list. 2025-09-07T06:09:28.8698538Z  2025-09-07T06:09:28.8699128Z  @User1,-lf,split_build 2025-09-07T06:09:28.8699677Z  @User2,lf 2025-09-07T06:09:28.8700197Z  @User3,split_build 2025-09-07T06:09:28.8700784Z """ 2025-09-07T06:09:28.8701243Z  2025-09-07T06:09:28.8701711Z import json 2025-09-07T06:09:28.8702269Z import logging 2025-09-07T06:09:28.8702872Z import os 2025-09-07T06:09:28.8703348Z import random 2025-09-07T06:09:28.8703943Z import re 2025-09-07T06:09:28.8704408Z import sys 2025-09-07T06:09:28.8704983Z from argparse import ArgumentParser 2025-09-07T06:09:28.8705711Z from collections.abc import Iterable 2025-09-07T06:09:28.8706626Z from functools import cache 2025-09-07T06:09:28.8707243Z from logging import LogRecord 2025-09-07T06:09:28.8707885Z from typing import Any, NamedTuple 2025-09-07T06:09:28.8708593Z from urllib.request import Request, urlopen 2025-09-07T06:09:28.8709232Z  2025-09-07T06:09:28.8709721Z import yaml 2025-09-07T06:09:28.8710265Z from github import Auth, Github 2025-09-07T06:09:28.8710863Z from github.Issue import Issue 2025-09-07T06:09:28.8711475Z  2025-09-07T06:09:28.8711902Z  2025-09-07T06:09:28.8712462Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:28.8713237Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:28.8714278Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:28.8715268Z  2025-09-07T06:09:28.8715766Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:28.8716678Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:28.8717706Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:28.8718384Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:28.8719072Z  2025-09-07T06:09:28.8719538Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:28.8720140Z  2025-09-07T06:09:28.8720639Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:28.8721263Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:28.8721780Z  2025-09-07T06:09:28.8722266Z  2025-09-07T06:09:28.8722711Z class Experiment(NamedTuple): 2025-09-07T06:09:28.8723323Z  rollout_perc: float = ( 2025-09-07T06:09:28.8724149Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:28.8724939Z  ) 2025-09-07T06:09:28.8725438Z  all_branches: bool = ( 2025-09-07T06:09:28.8726772Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:28.8728183Z  ) 2025-09-07T06:09:28.8728879Z  default: bool = ( 2025-09-07T06:09:28.8730101Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:28.8731443Z  ) 2025-09-07T06:09:28.8732125Z  2025-09-07T06:09:28.8732916Z  # Add more fields as needed 2025-09-07T06:09:28.8733863Z  2025-09-07T06:09:28.8734584Z  2025-09-07T06:09:28.8735269Z class Settings(NamedTuple): 2025-09-07T06:09:28.8736595Z  """ 2025-09-07T06:09:28.8737594Z  Settings for the experiments that can be opted into. 2025-09-07T06:09:28.8738663Z  """ 2025-09-07T06:09:28.8739535Z  2025-09-07T06:09:28.8740348Z  experiments: dict[str, Experiment] = {} 2025-09-07T06:09:28.8741389Z  2025-09-07T06:09:28.8742466Z  2025-09-07T06:09:28.8743376Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:28.8744644Z  """Color codes the log messages based on the log level""" 2025-09-07T06:09:28.8746248Z  2025-09-07T06:09:28.8747078Z  COLORS = { 2025-09-07T06:09:28.8747973Z  "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:28.8749314Z  "ERROR": "\033[31m", # Red 2025-09-07T06:09:28.8750516Z  "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:28.8751646Z  "INFO": "\033[0m", # Reset 2025-09-07T06:09:28.8752782Z  "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:28.8753784Z  } 2025-09-07T06:09:28.8754687Z  2025-09-07T06:09:28.8755692Z  def format(self, record: LogRecord) -> str: 2025-09-07T06:09:28.8757736Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:28.8759297Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:28.8760669Z  return super().format(record) 2025-09-07T06:09:28.8761766Z  2025-09-07T06:09:28.8762456Z  2025-09-07T06:09:28.8763469Z handler = logging.StreamHandler() 2025-09-07T06:09:28.8764963Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:28.8766713Z  2025-09-07T06:09:28.8767774Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:28.8769027Z log.addHandler(handler) 2025-09-07T06:09:28.8770032Z log.setLevel(logging.INFO) 2025-09-07T06:09:28.8771089Z  2025-09-07T06:09:28.8771937Z  2025-09-07T06:09:28.8772956Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:28.8774393Z  """ 2025-09-07T06:09:28.8775516Z  Defines outputs of the github action that invokes this script 2025-09-07T06:09:28.8777376Z  """ 2025-09-07T06:09:28.8778334Z  if not GITHUB_OUTPUT: 2025-09-07T06:09:28.8780471Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:28.8782688Z  log.warning( 2025-09-07T06:09:28.8784309Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T06:09:28.8786451Z  ) 2025-09-07T06:09:28.8787553Z  print(f"::set-output name={key}::{value}") 2025-09-07T06:09:28.8788615Z  return 2025-09-07T06:09:28.8789607Z  2025-09-07T06:09:28.8790336Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:28.8791568Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:28.8792732Z  f.write(f"{key}={value}\n") 2025-09-07T06:09:28.8793895Z  2025-09-07T06:09:28.8794809Z  2025-09-07T06:09:28.8796253Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:28.8797755Z  return frozenset( 2025-09-07T06:09:28.8799490Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:28.8801097Z  ) 2025-09-07T06:09:28.8801896Z  2025-09-07T06:09:28.8802883Z  2025-09-07T06:09:28.8803702Z def parse_args() -> Any: 2025-09-07T06:09:28.8804809Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:28.8806972Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:28.8808453Z  parser.add_argument( 2025-09-07T06:09:28.8809458Z  "--github-issue-repo", 2025-09-07T06:09:28.8810448Z  type=str, 2025-09-07T06:09:28.8811534Z  required=False, 2025-09-07T06:09:28.8812738Z  default="pytorch/test-infra", 2025-09-07T06:09:28.8813898Z  help="GitHub repo to get the issue", 2025-09-07T06:09:28.8815000Z  ) 2025-09-07T06:09:28.8816191Z  parser.add_argument( 2025-09-07T06:09:28.8817247Z  "--github-repo", 2025-09-07T06:09:28.8818208Z  type=str, 2025-09-07T06:09:28.8819099Z  required=True, 2025-09-07T06:09:28.8820162Z  help="GitHub repo where CI is running", 2025-09-07T06:09:28.8821226Z  ) 2025-09-07T06:09:28.8822081Z  parser.add_argument( 2025-09-07T06:09:28.8823347Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:28.8824707Z  ) 2025-09-07T06:09:28.8825457Z  parser.add_argument( 2025-09-07T06:09:28.8827031Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:28.8828416Z  ) 2025-09-07T06:09:28.8829149Z  parser.add_argument( 2025-09-07T06:09:28.8898453Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:28.8899610Z  ) 2025-09-07T06:09:28.8900256Z  parser.add_argument( 2025-09-07T06:09:28.8901397Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:28.8902688Z  ) 2025-09-07T06:09:28.8903378Z  parser.add_argument( 2025-09-07T06:09:28.8904191Z  "--github-ref-type", 2025-09-07T06:09:28.8905009Z  type=str, 2025-09-07T06:09:28.8905739Z  required=True, 2025-09-07T06:09:28.8906982Z  help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:28.8907893Z  ) 2025-09-07T06:09:28.8908569Z  parser.add_argument( 2025-09-07T06:09:28.8909800Z  "--eligible-experiments", 2025-09-07T06:09:28.8910739Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:28.8911674Z  required=False, 2025-09-07T06:09:28.8912469Z  default="", 2025-09-07T06:09:28.8913971Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:28.8915588Z  ) 2025-09-07T06:09:28.8916493Z  parser.add_argument( 2025-09-07T06:09:28.8917341Z  "--opt-out-experiments", 2025-09-07T06:09:28.8918295Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:28.8919192Z  required=False, 2025-09-07T06:09:28.8919947Z  default="", 2025-09-07T06:09:28.8920674Z  help=( 2025-09-07T06:09:28.8921890Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:28.8923873Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:28.8925325Z  ), 2025-09-07T06:09:28.8926200Z  ) 2025-09-07T06:09:28.8926862Z  parser.add_argument( 2025-09-07T06:09:28.8927682Z  "--pr-number", 2025-09-07T06:09:28.8928466Z  type=str, 2025-09-07T06:09:28.8929187Z  required=False, 2025-09-07T06:09:28.8929972Z  default="", 2025-09-07T06:09:28.8930885Z  help="the optional PR number where this is run", 2025-09-07T06:09:28.8931847Z  ) 2025-09-07T06:09:28.8932431Z  2025-09-07T06:09:28.8933070Z  return parser.parse_args() 2025-09-07T06:09:28.8933908Z  2025-09-07T06:09:28.8934467Z  2025-09-07T06:09:28.8935492Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.8937287Z  auth = Auth.Token(github_token) 2025-09-07T06:09:28.8938218Z  return Github(auth=auth) 2025-09-07T06:09:28.8938995Z  2025-09-07T06:09:28.8939574Z  2025-09-07T06:09:28.8940690Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.8942084Z  repo = gh.get_repo(repo) 2025-09-07T06:09:28.8943015Z  return repo.get_issue(number=issue_num) 2025-09-07T06:09:28.8943901Z  2025-09-07T06:09:28.8944470Z  2025-09-07T06:09:28.8945079Z def get_potential_pr_author( 2025-09-07T06:09:28.8947089Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:28.8948279Z ) -> str: 2025-09-07T06:09:28.8949212Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:28.8950627Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:28.8952018Z  # embedded in the tag name: ciflow// 2025-09-07T06:09:28.8953144Z  2025-09-07T06:09:28.8953792Z  gh = get_gh_client(github_token) 2025-09-07T06:09:28.8954637Z  2025-09-07T06:09:28.8955409Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:28.8956816Z  split_tag = ref_name.split("/") 2025-09-07T06:09:28.8957683Z  if ( 2025-09-07T06:09:28.8958364Z  len(split_tag) == 3 2025-09-07T06:09:28.8959248Z  and split_tag[0] == "ciflow" 2025-09-07T06:09:28.8960196Z  and split_tag[2].isnumeric() 2025-09-07T06:09:28.8961055Z  ): 2025-09-07T06:09:28.8961742Z  pr_number = split_tag[2] 2025-09-07T06:09:28.8962617Z  try: 2025-09-07T06:09:28.8963432Z  repository = gh.get_repo(repo) 2025-09-07T06:09:28.8964746Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:28.8965999Z  except Exception as e: 2025-09-07T06:09:28.8966942Z  raise Exception( # noqa: TRY002 2025-09-07T06:09:28.8968097Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:28.8969215Z  ) from e 2025-09-07T06:09:28.8970202Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:28.8971499Z  # In all other cases, return the original input username 2025-09-07T06:09:28.8972544Z  return username 2025-09-07T06:09:28.8973286Z  2025-09-07T06:09:28.8973847Z  2025-09-07T06:09:28.8974553Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:28.8975477Z  """ 2025-09-07T06:09:28.8976850Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:28.8978216Z  """ 2025-09-07T06:09:28.8979200Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:28.8980346Z  2025-09-07T06:09:28.8980904Z  2025-09-07T06:09:28.8981558Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:28.8982432Z  try: 2025-09-07T06:09:28.8983126Z  data = yaml.safe_load(yaml_text) 2025-09-07T06:09:28.8983989Z  return data 2025-09-07T06:09:28.8984800Z  except yaml.YAMLError: 2025-09-07T06:09:28.8985738Z  log.exception("Error loading YAML") 2025-09-07T06:09:28.8986819Z  raise 2025-09-07T06:09:28.8987471Z  2025-09-07T06:09:28.8988081Z  2025-09-07T06:09:28.8989181Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:28.8990487Z  """ 2025-09-07T06:09:28.8991808Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:28.8993132Z  2025-09-07T06:09:28.8994046Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.8995380Z  and the text below is the list of opted in users. 2025-09-07T06:09:28.8996536Z  2025-09-07T06:09:28.8997501Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:28.8998728Z  """ 2025-09-07T06:09:28.8999539Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:28.9000568Z  if len(rollout_state_parts) >= 2: 2025-09-07T06:09:28.9001660Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:28.9002686Z  else: 2025-09-07T06:09:28.9003352Z  return "", rollout_state 2025-09-07T06:09:28.9004203Z  2025-09-07T06:09:28.9004789Z  2025-09-07T06:09:28.9005496Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:28.9006576Z  """ 2025-09-07T06:09:28.9007518Z  Dictionary of users with a list of features they have opted into 2025-09-07T06:09:28.9008674Z  """ 2025-09-07T06:09:28.9009277Z  2025-09-07T06:09:28.9009827Z  2025-09-07T06:09:28.9010745Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:28.9011904Z  """ 2025-09-07T06:09:28.9013133Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T06:09:28.9014596Z  2025-09-07T06:09:28.9016163Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:28.9017920Z  - Example line: "@User1,lf,split_build" 2025-09-07T06:09:28.9019380Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:28.9020499Z  2025-09-07T06:09:28.9021064Z  2025-09-07T06:09:28.9021617Z  """ 2025-09-07T06:09:28.9022250Z  optins = UserOptins() 2025-09-07T06:09:28.9023157Z  for user in user_optin_text.split("\n"): 2025-09-07T06:09:28.9024132Z  user = user.strip("\r\n\t -") 2025-09-07T06:09:28.9025080Z  if not user or not user.startswith("@"): 2025-09-07T06:09:28.9026307Z  # Not a valid user. Skip 2025-09-07T06:09:28.9027188Z  continue 2025-09-07T06:09:28.9027896Z  2025-09-07T06:09:28.9028468Z  if user: 2025-09-07T06:09:28.9029279Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:28.9030492Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:28.9031597Z  2025-09-07T06:09:28.9032193Z  return optins 2025-09-07T06:09:28.9032911Z  2025-09-07T06:09:28.9033469Z  2025-09-07T06:09:28.9034306Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:28.9035370Z  """ 2025-09-07T06:09:28.9036312Z  Check if the experiment name is valid. 2025-09-07T06:09:28.9037239Z  A valid name: 2025-09-07T06:09:28.9038374Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:28.9040029Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:28.9041258Z  - Cannot contain spaces 2025-09-07T06:09:28.9042067Z  """ 2025-09-07T06:09:28.9042688Z  2025-09-07T06:09:28.9043450Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:28.9044676Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:28.9046112Z  2025-09-07T06:09:28.9046766Z  if valid: 2025-09-07T06:09:28.9047486Z  return True 2025-09-07T06:09:28.9048225Z  2025-09-07T06:09:28.9048802Z  log.error( 2025-09-07T06:09:28.9051293Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-09-07T06:09:28.9054040Z  ) 2025-09-07T06:09:28.9054687Z  return False 2025-09-07T06:09:28.9055370Z  2025-09-07T06:09:28.9056125Z  2025-09-07T06:09:28.9057039Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:28.9058188Z  """ 2025-09-07T06:09:28.9059236Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:28.9060474Z  """ 2025-09-07T06:09:28.9061104Z  try: 2025-09-07T06:09:28.9061780Z  if settings_text: 2025-09-07T06:09:28.9063057Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:28.9064404Z  # for easy reading 2025-09-07T06:09:28.9065983Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:28.9067544Z  # the backtick character in shell commands. 2025-09-07T06:09:28.9068612Z  backtick = chr(96) # backtick character 2025-09-07T06:09:28.9069754Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:28.9070957Z  settings = load_yaml(settings_text) 2025-09-07T06:09:28.9071851Z  2025-09-07T06:09:28.9073104Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:28.9074389Z  experiments = {} 2025-09-07T06:09:28.9075195Z  2025-09-07T06:09:28.9076346Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:28.9077660Z  if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:28.9079532Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-09-07T06:09:28.9081344Z  continue 2025-09-07T06:09:28.9082126Z  2025-09-07T06:09:28.9082748Z  valid_settings = {} 2025-09-07T06:09:28.9083672Z  for setting in exp_settings: 2025-09-07T06:09:28.9084676Z  if setting not in Experiment._fields: 2025-09-07T06:09:28.9085636Z  log.warning( 2025-09-07T06:09:28.9087092Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:28.9088338Z  ) 2025-09-07T06:09:28.9089105Z  else: 2025-09-07T06:09:28.9090065Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:28.9091074Z  2025-09-07T06:09:28.9091891Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:28.9092993Z  return Settings(experiments) 2025-09-07T06:09:28.9093848Z  2025-09-07T06:09:28.9094450Z  except Exception: 2025-09-07T06:09:28.9095301Z  log.exception("Failed to parse settings") 2025-09-07T06:09:28.9096445Z  2025-09-07T06:09:28.9097081Z  return Settings() 2025-09-07T06:09:28.9097826Z  2025-09-07T06:09:28.9098387Z  2025-09-07T06:09:28.9099373Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:28.9100413Z  """ 2025-09-07T06:09:28.9101187Z  Parse settings, if any, from the rollout state. 2025-09-07T06:09:28.9102125Z  2025-09-07T06:09:28.9103062Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.9104368Z  and the text below is the list of opted in users. 2025-09-07T06:09:28.9105305Z  2025-09-07T06:09:28.9106523Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:28.9107778Z  """ 2025-09-07T06:09:28.9108737Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.9110082Z  return parse_settings_from_text(settings_text) 2025-09-07T06:09:28.9111019Z  2025-09-07T06:09:28.9111593Z  2025-09-07T06:09:28.9112372Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:28.9113350Z  """ 2025-09-07T06:09:28.9114038Z  Parse users from the rollout state. 2025-09-07T06:09:28.9114895Z  2025-09-07T06:09:28.9115495Z  """ 2025-09-07T06:09:28.9116707Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.9117989Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:28.9118914Z  2025-09-07T06:09:28.9119495Z  2025-09-07T06:09:28.9120548Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.9121833Z  """ 2025-09-07T06:09:28.9122610Z  Check if a user is opted into an experiment 2025-09-07T06:09:28.9123502Z  """ 2025-09-07T06:09:28.9124313Z  return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:28.9125561Z  2025-09-07T06:09:28.9126350Z  2025-09-07T06:09:28.9127419Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.9128715Z  """ 2025-09-07T06:09:28.9129530Z  Check if a user explicitly opted out of an experiment 2025-09-07T06:09:28.9130520Z  """ 2025-09-07T06:09:28.9131403Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:28.9132618Z  experiment_optout = "-" + experiment_name 2025-09-07T06:09:28.9133732Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:28.9134758Z  return False 2025-09-07T06:09:28.9135487Z  2025-09-07T06:09:28.9136449Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:28.9137466Z  log.warning( 2025-09-07T06:09:28.9138921Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:28.9140411Z  ) 2025-09-07T06:09:28.9141042Z  2025-09-07T06:09:28.9141638Z  return True 2025-09-07T06:09:28.9142319Z  2025-09-07T06:09:28.9142862Z  2025-09-07T06:09:28.9143453Z def get_runner_prefix( 2025-09-07T06:09:28.9144254Z  rollout_state: str, 2025-09-07T06:09:28.9145104Z  workflow_requestors: Iterable[str], 2025-09-07T06:09:28.9146162Z  branch: str, 2025-09-07T06:09:28.9147051Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.9148231Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.9149237Z  is_canary: bool = False, 2025-09-07T06:09:28.9150018Z ) -> str: 2025-09-07T06:09:28.9150790Z  settings = parse_settings(rollout_state) 2025-09-07T06:09:28.9151791Z  user_optins = parse_users(rollout_state) 2025-09-07T06:09:28.9152684Z  2025-09-07T06:09:28.9153508Z  fleet_prefix = "" 2025-09-07T06:09:28.9154338Z  prefixes = [] 2025-09-07T06:09:28.9155499Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:28.9157348Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:28.9158555Z  log.info( 2025-09-07T06:09:28.9159740Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:28.9161046Z  ) 2025-09-07T06:09:28.9161725Z  continue 2025-09-07T06:09:28.9162418Z  2025-09-07T06:09:28.9163035Z  if opt_out_experiments: 2025-09-07T06:09:28.9164009Z  if experiment_name in opt_out_experiments: 2025-09-07T06:09:28.9165095Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:28.9166303Z  log.info( 2025-09-07T06:09:28.9167936Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:28.9169597Z  ) 2025-09-07T06:09:28.9170339Z  continue 2025-09-07T06:09:28.9171120Z  2025-09-07T06:09:28.9171742Z  if eligible_experiments: 2025-09-07T06:09:28.9172716Z  if experiment_name not in eligible_experiments: 2025-09-07T06:09:28.9173835Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:28.9174775Z  log.info( 2025-09-07T06:09:28.9176315Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:28.9177769Z  ) 2025-09-07T06:09:28.9178681Z  continue 2025-09-07T06:09:28.9179581Z  elif not experiment_settings.default: 2025-09-07T06:09:28.9180510Z  log.info( 2025-09-07T06:09:28.9181669Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:28.9182921Z  ) 2025-09-07T06:09:28.9183630Z  continue 2025-09-07T06:09:28.9184336Z  2025-09-07T06:09:28.9185116Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:28.9186387Z  opted_out_users = [ 2025-09-07T06:09:28.9187209Z  requestor 2025-09-07T06:09:28.9188034Z  for requestor in workflow_requestors 2025-09-07T06:09:28.9189206Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.9190295Z  ] 2025-09-07T06:09:28.9190938Z  2025-09-07T06:09:28.9191527Z  if opted_out_users: 2025-09-07T06:09:28.9192329Z  log.info( 2025-09-07T06:09:28.9193443Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:28.9194626Z  ) 2025-09-07T06:09:28.9195312Z  continue 2025-09-07T06:09:28.9196242Z  2025-09-07T06:09:28.9197051Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:28.9198097Z  opted_in_users = [ 2025-09-07T06:09:28.9198881Z  requestor 2025-09-07T06:09:28.9199727Z  for requestor in workflow_requestors 2025-09-07T06:09:28.9200848Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.9201900Z  ] 2025-09-07T06:09:28.9202528Z  2025-09-07T06:09:28.9203139Z  enabled = False 2025-09-07T06:09:28.9203923Z  if opted_in_users: 2025-09-07T06:09:28.9204905Z  log.info( 2025-09-07T06:09:28.9206211Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:28.9207388Z  ) 2025-09-07T06:09:28.9208061Z  enabled = True 2025-09-07T06:09:28.9208823Z  2025-09-07T06:09:28.9209509Z  elif experiment_settings.rollout_perc: 2025-09-07T06:09:28.9210912Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:28.9212546Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:28.9213651Z  log.info( 2025-09-07T06:09:28.9215152Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:28.9216936Z  ) 2025-09-07T06:09:28.9217675Z  enabled = True 2025-09-07T06:09:28.9218484Z  2025-09-07T06:09:28.9219070Z  if enabled: 2025-09-07T06:09:28.9219836Z  label = experiment_name 2025-09-07T06:09:28.9220788Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:28.9222195Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:28.9223685Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:28.9224998Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:28.9226361Z  if is_canary: 2025-09-07T06:09:28.9227239Z  label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:28.9228190Z  fleet_prefix = label 2025-09-07T06:09:28.9229260Z  else: 2025-09-07T06:09:28.9230022Z  prefixes.append(label) 2025-09-07T06:09:28.9230894Z  2025-09-07T06:09:28.9231502Z  if len(prefixes) > 1: 2025-09-07T06:09:28.9232284Z  log.error( 2025-09-07T06:09:28.9234081Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-09-07T06:09:28.9236187Z  ) 2025-09-07T06:09:28.9236901Z  prefixes = prefixes[:1] 2025-09-07T06:09:28.9237697Z  2025-09-07T06:09:28.9238309Z  # Fleet always comes first 2025-09-07T06:09:28.9239132Z  if fleet_prefix: 2025-09-07T06:09:28.9239968Z  prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:28.9240829Z  2025-09-07T06:09:28.9241573Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:28.9242528Z  2025-09-07T06:09:28.9243112Z  2025-09-07T06:09:28.9244193Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:28.9245491Z  """ 2025-09-07T06:09:28.9246730Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:28.9247970Z  2025-09-07T06:09:28.9248939Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:28.9250168Z  """ 2025-09-07T06:09:28.9250859Z  gh = get_gh_client(github_token) 2025-09-07T06:09:28.9251789Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:28.9252907Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:28.9253930Z  2025-09-07T06:09:28.9254480Z  2025-09-07T06:09:28.9255491Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:28.9257210Z  for _ in range(num_retries): 2025-09-07T06:09:28.9258053Z  try: 2025-09-07T06:09:28.9258826Z  req = Request(url=url, headers=headers) 2025-09-07T06:09:28.9259955Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:28.9261048Z  return json.loads(content) 2025-09-07T06:09:28.9261957Z  except Exception as e: 2025-09-07T06:09:28.9262929Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:28.9263847Z  2025-09-07T06:09:28.9264821Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:28.9266238Z  return {} 2025-09-07T06:09:28.9266899Z  2025-09-07T06:09:28.9267450Z  2025-09-07T06:09:28.9268032Z @cache 2025-09-07T06:09:28.9269119Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:28.9270414Z  """ 2025-09-07T06:09:28.9271181Z  Dynamically get PR information 2025-09-07T06:09:28.9272035Z  """ 2025-09-07T06:09:28.9272895Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:28.9273950Z  headers = { 2025-09-07T06:09:28.9274779Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:28.9276072Z  "Authorization": f"token {github_token}", 2025-09-07T06:09:28.9276977Z  } 2025-09-07T06:09:28.9277751Z  json_response: dict[str, Any] = download_json( 2025-09-07T06:09:28.9278793Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:28.9279697Z  headers=headers, 2025-09-07T06:09:28.9280478Z  ) 2025-09-07T06:09:28.9281080Z  2025-09-07T06:09:28.9281693Z  if not json_response: 2025-09-07T06:09:28.9282916Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:28.9283997Z  return {} 2025-09-07T06:09:28.9284701Z  2025-09-07T06:09:28.9285303Z  return json_response 2025-09-07T06:09:28.9286240Z  2025-09-07T06:09:28.9286810Z  2025-09-07T06:09:28.9287839Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:28.9289044Z  """ 2025-09-07T06:09:28.9289966Z  Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:28.9291204Z  """ 2025-09-07T06:09:28.9292066Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:28.9293081Z  return { 2025-09-07T06:09:28.9294123Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:28.9295300Z  } 2025-09-07T06:09:28.9296070Z  2025-09-07T06:09:28.9296665Z  2025-09-07T06:09:28.9297285Z def main() -> None: 2025-09-07T06:09:28.9298046Z  args = parse_args() 2025-09-07T06:09:28.9298784Z  2025-09-07T06:09:28.9299468Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:28.9300396Z  2025-09-07T06:09:28.9301030Z  # Check if the PR is opt-out 2025-09-07T06:09:28.9301874Z  if args.pr_number: 2025-09-07T06:09:28.9303048Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:28.9304376Z  if OPT_OUT_LABEL in labels: 2025-09-07T06:09:28.9305226Z  log.info( 2025-09-07T06:09:28.9306623Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:28.9307977Z  ) 2025-09-07T06:09:28.9308959Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.9310136Z  sys.exit() 2025-09-07T06:09:28.9311080Z  2025-09-07T06:09:28.9311677Z  try: 2025-09-07T06:09:28.9312428Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:28.9313662Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:28.9314743Z  ) 2025-09-07T06:09:28.9315356Z  2025-09-07T06:09:28.9316327Z  username = get_potential_pr_author( 2025-09-07T06:09:28.9317291Z  args.github_token, 2025-09-07T06:09:28.9318141Z  args.github_repo, 2025-09-07T06:09:28.9318964Z  args.github_actor, 2025-09-07T06:09:28.9319847Z  args.github_ref_type, 2025-09-07T06:09:28.9320717Z  args.github_branch, 2025-09-07T06:09:28.9321507Z  ) 2025-09-07T06:09:28.9322107Z  2025-09-07T06:09:28.9322955Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:28.9323985Z  2025-09-07T06:09:28.9324660Z  runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:28.9325623Z  rollout_state, 2025-09-07T06:09:28.9326683Z  (args.github_issue_owner, username), 2025-09-07T06:09:28.9327630Z  args.github_branch, 2025-09-07T06:09:28.9328499Z  args.eligible_experiments, 2025-09-07T06:09:28.9329422Z  args.opt_out_experiments, 2025-09-07T06:09:28.9330316Z  is_canary, 2025-09-07T06:09:28.9331039Z  ) 2025-09-07T06:09:28.9331659Z  2025-09-07T06:09:28.9332259Z  except Exception as e: 2025-09-07T06:09:28.9333073Z  log.error( 2025-09-07T06:09:28.9334262Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:28.9335767Z  ) 2025-09-07T06:09:28.9336717Z  2025-09-07T06:09:28.9337640Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.9338750Z  2025-09-07T06:09:28.9339340Z  2025-09-07T06:09:28.9339965Z if __name__ == "__main__": 2025-09-07T06:09:28.9340745Z  main() 2025-09-07T06:09:28.9341373Z  2025-09-07T06:09:28.9341918Z EOF 2025-09-07T06:09:28.9342513Z  2025-09-07T06:09:28.9343109Z cat runner_determinator.py 2025-09-07T06:09:29.0822033Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:29.0822853Z env: 2025-09-07T06:09:29.0823540Z GITHUB_TOKEN: *** 2025-09-07T06:09:29.0823935Z ISSUE_NUMBER: 5132 2025-09-07T06:09:29.0824363Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:29.0824832Z ISSUE_OWNER: 2025-09-07T06:09:29.0825209Z CHECK_EXPERIMENTS: 2025-09-07T06:09:29.0825605Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:29.0826412Z PR_NUMBER: 2025-09-07T06:09:29.0827161Z ##[endgroup] 2025-09-07T06:09:29.1038139Z # flake8: noqa: G004 2025-09-07T06:09:29.1038496Z 2025-09-07T06:09:29.1038928Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:29.1039855Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:29.1040675Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:29.1041104Z 2025-09-07T06:09:29.1041258Z """ 2025-09-07T06:09:29.1041826Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:29.1042685Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:29.1043553Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:29.1044359Z of which runners should be used to run which job. 2025-09-07T06:09:29.1044746Z 2025-09-07T06:09:29.1045113Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:29.1046517Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:29.1047421Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:29.1048103Z list, defined. 2025-09-07T06:09:29.1048334Z 2025-09-07T06:09:29.1048690Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:29.1049561Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:29.1050358Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:29.1050785Z 2025-09-07T06:09:29.1051136Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:29.1051968Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:29.1052668Z experiments which the user could be opted in to. 2025-09-07T06:09:29.1053061Z 2025-09-07T06:09:29.1053254Z The user list has the following rules: 2025-09-07T06:09:29.1053606Z 2025-09-07T06:09:29.1053917Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:29.1054741Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:29.1055478Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:29.1056074Z 2025-09-07T06:09:29.1056343Z Example config: 2025-09-07T06:09:29.1056820Z # A list of experiments that can be opted into. 2025-09-07T06:09:29.1057475Z # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:29.1058071Z # Expected syntax is: 2025-09-07T06:09:29.1058699Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:29.1059647Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:29.1060246Z 2025-09-07T06:09:29.1060408Z experiments: 2025-09-07T06:09:29.1060791Z lf: 2025-09-07T06:09:29.1061180Z rollout_percent: 25 2025-09-07T06:09:29.1061808Z all_branches: false 2025-09-07T06:09:29.1062261Z default: true 2025-09-07T06:09:29.1062669Z --- 2025-09-07T06:09:29.1062874Z 2025-09-07T06:09:29.1063029Z # Opt-ins: 2025-09-07T06:09:29.1063598Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:29.1064421Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:29.1065164Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:29.1065797Z # Experiments should be from the above list. 2025-09-07T06:09:29.1066441Z 2025-09-07T06:09:29.1066628Z @User1,-lf,split_build 2025-09-07T06:09:29.1067086Z @User2,lf 2025-09-07T06:09:29.1067480Z @User3,split_build 2025-09-07T06:09:29.1067886Z """ 2025-09-07T06:09:29.1068084Z 2025-09-07T06:09:29.1068242Z import json 2025-09-07T06:09:29.1068612Z import logging 2025-09-07T06:09:29.1068994Z import os 2025-09-07T06:09:29.1069363Z import random 2025-09-07T06:09:29.1069745Z import re 2025-09-07T06:09:29.1070110Z import sys 2025-09-07T06:09:29.1070500Z from argparse import ArgumentParser 2025-09-07T06:09:29.1071019Z from collections.abc import Iterable 2025-09-07T06:09:29.1071530Z from functools import cache 2025-09-07T06:09:29.1071999Z from logging import LogRecord 2025-09-07T06:09:29.1072482Z from typing import Any, NamedTuple 2025-09-07T06:09:29.1073000Z from urllib.request import Request, urlopen 2025-09-07T06:09:29.1073365Z 2025-09-07T06:09:29.1073536Z import yaml 2025-09-07T06:09:29.1073935Z from github import Auth, Github 2025-09-07T06:09:29.1074425Z from github.Issue import Issue 2025-09-07T06:09:29.1074729Z 2025-09-07T06:09:29.1074735Z 2025-09-07T06:09:29.1074945Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:29.1075611Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:29.1076775Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:29.1077317Z 2025-09-07T06:09:29.1077549Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:29.1078259Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:29.1078774Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:29.1079318Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:29.1079667Z 2025-09-07T06:09:29.1079854Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:29.1080188Z 2025-09-07T06:09:29.1080365Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:29.1080821Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:29.1081108Z 2025-09-07T06:09:29.1081114Z 2025-09-07T06:09:29.1081296Z class Experiment(NamedTuple): 2025-09-07T06:09:29.1081775Z rollout_perc: float = ( 2025-09-07T06:09:29.1082387Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:29.1083041Z ) 2025-09-07T06:09:29.1083409Z all_branches: bool = ( 2025-09-07T06:09:29.1084015Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:29.1084667Z ) 2025-09-07T06:09:29.1085030Z default: bool = ( 2025-09-07T06:09:29.1085578Z True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:29.1086385Z ) 2025-09-07T06:09:29.1086585Z 2025-09-07T06:09:29.1086766Z # Add more fields as needed 2025-09-07T06:09:29.1087062Z 2025-09-07T06:09:29.1087068Z 2025-09-07T06:09:29.1087247Z class Settings(NamedTuple): 2025-09-07T06:09:29.1087687Z """ 2025-09-07T06:09:29.1088129Z Settings for the experiments that can be opted into. 2025-09-07T06:09:29.1088689Z """ 2025-09-07T06:09:29.1088894Z 2025-09-07T06:09:29.1089091Z experiments: dict[str, Experiment] = {} 2025-09-07T06:09:29.1089454Z 2025-09-07T06:09:29.1089462Z 2025-09-07T06:09:29.1089666Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:29.1090273Z """Color codes the log messages based on the log level""" 2025-09-07T06:09:29.1090695Z 2025-09-07T06:09:29.1090854Z COLORS = { 2025-09-07T06:09:29.1091252Z "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:29.1091884Z "ERROR": "\033[31m", # Red 2025-09-07T06:09:29.1092375Z "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:29.1092912Z "INFO": "\033[0m", # Reset 2025-09-07T06:09:29.1093394Z "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:29.1093855Z } 2025-09-07T06:09:29.1094051Z 2025-09-07T06:09:29.1094259Z def format(self, record: LogRecord) -> str: 2025-09-07T06:09:29.1094983Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:29.1095721Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:29.1096490Z return super().format(record) 2025-09-07T06:09:29.1096823Z 2025-09-07T06:09:29.1096831Z 2025-09-07T06:09:29.1097016Z handler = logging.StreamHandler() 2025-09-07T06:09:29.1097699Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:29.1098236Z 2025-09-07T06:09:29.1098470Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:29.1099040Z log.addHandler(handler) 2025-09-07T06:09:29.1099483Z log.setLevel(logging.INFO) 2025-09-07T06:09:29.1099761Z 2025-09-07T06:09:29.1099767Z 2025-09-07T06:09:29.1100002Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:29.1100551Z """ 2025-09-07T06:09:29.1101033Z Defines outputs of the github action that invokes this script 2025-09-07T06:09:29.1101640Z """ 2025-09-07T06:09:29.1102009Z if not GITHUB_OUTPUT: 2025-09-07T06:09:29.1103043Z # 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.1104116Z log.warning( 2025-09-07T06:09:29.1104928Z "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.1105937Z ) 2025-09-07T06:09:29.1117474Z print(f"::set-output name={key}::{value}") 2025-09-07T06:09:29.1118087Z return 2025-09-07T06:09:29.1118335Z 2025-09-07T06:09:29.1118701Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:29.1119302Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:29.1119868Z f.write(f"{key}={value}\n") 2025-09-07T06:09:29.1120195Z 2025-09-07T06:09:29.1120203Z 2025-09-07T06:09:29.1120490Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:29.1121106Z return frozenset( 2025-09-07T06:09:29.1121700Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:29.1122361Z ) 2025-09-07T06:09:29.1122562Z 2025-09-07T06:09:29.1122568Z 2025-09-07T06:09:29.1122746Z def parse_args() -> Any: 2025-09-07T06:09:29.1123279Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:29.1124109Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:29.1124836Z parser.add_argument( 2025-09-07T06:09:29.1125284Z "--github-issue-repo", 2025-09-07T06:09:29.1125726Z type=str, 2025-09-07T06:09:29.1126361Z required=False, 2025-09-07T06:09:29.1126805Z default="pytorch/test-infra", 2025-09-07T06:09:29.1127328Z help="GitHub repo to get the issue", 2025-09-07T06:09:29.1127822Z ) 2025-09-07T06:09:29.1128187Z parser.add_argument( 2025-09-07T06:09:29.1128629Z "--github-repo", 2025-09-07T06:09:29.1129038Z type=str, 2025-09-07T06:09:29.1129438Z required=True, 2025-09-07T06:09:29.1129885Z help="GitHub repo where CI is running", 2025-09-07T06:09:29.1130397Z ) 2025-09-07T06:09:29.1130761Z parser.add_argument( 2025-09-07T06:09:29.1131361Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:29.1132002Z ) 2025-09-07T06:09:29.1132366Z parser.add_argument( 2025-09-07T06:09:29.1132981Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:29.1133626Z ) 2025-09-07T06:09:29.1134135Z parser.add_argument( 2025-09-07T06:09:29.1134755Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:29.1135422Z ) 2025-09-07T06:09:29.1135784Z parser.add_argument( 2025-09-07T06:09:29.1136622Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:29.1137308Z ) 2025-09-07T06:09:29.1137678Z parser.add_argument( 2025-09-07T06:09:29.1138114Z "--github-ref-type", 2025-09-07T06:09:29.1138563Z type=str, 2025-09-07T06:09:29.1139046Z required=True, 2025-09-07T06:09:29.1139536Z help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:29.1140073Z ) 2025-09-07T06:09:29.1140440Z parser.add_argument( 2025-09-07T06:09:29.1140898Z "--eligible-experiments", 2025-09-07T06:09:29.1141400Z type=_str_comma_separated_to_set, 2025-09-07T06:09:29.1141907Z required=False, 2025-09-07T06:09:29.1142312Z default="", 2025-09-07T06:09:29.1143143Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:29.1144025Z ) 2025-09-07T06:09:29.1144394Z parser.add_argument( 2025-09-07T06:09:29.1144844Z "--opt-out-experiments", 2025-09-07T06:09:29.1145337Z type=_str_comma_separated_to_set, 2025-09-07T06:09:29.1146021Z required=False, 2025-09-07T06:09:29.1146462Z default="", 2025-09-07T06:09:29.1146847Z help=( 2025-09-07T06:09:29.1147499Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:29.1148581Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:29.1149371Z ), 2025-09-07T06:09:29.1149727Z ) 2025-09-07T06:09:29.1150098Z parser.add_argument( 2025-09-07T06:09:29.1150519Z "--pr-number", 2025-09-07T06:09:29.1150921Z type=str, 2025-09-07T06:09:29.1151317Z required=False, 2025-09-07T06:09:29.1151728Z default="", 2025-09-07T06:09:29.1152345Z help="the optional PR number where this is run", 2025-09-07T06:09:29.1152901Z ) 2025-09-07T06:09:29.1153105Z 2025-09-07T06:09:29.1153286Z return parser.parse_args() 2025-09-07T06:09:29.1153596Z 2025-09-07T06:09:29.1153603Z 2025-09-07T06:09:29.1153988Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:29.1154710Z auth = Auth.Token(github_token) 2025-09-07T06:09:29.1155201Z return Github(auth=auth) 2025-09-07T06:09:29.1155492Z 2025-09-07T06:09:29.1155499Z 2025-09-07T06:09:29.1156192Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:29.1156993Z repo = gh.get_repo(repo) 2025-09-07T06:09:29.1157492Z return repo.get_issue(number=issue_num) 2025-09-07T06:09:29.1157851Z 2025-09-07T06:09:29.1157857Z 2025-09-07T06:09:29.1158041Z def get_potential_pr_author( 2025-09-07T06:09:29.1158678Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:29.1159328Z ) -> str: 2025-09-07T06:09:29.1159827Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:29.1160601Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:29.1161310Z # embedded in the tag name: ciflow// 2025-09-07T06:09:29.1161720Z 2025-09-07T06:09:29.1161899Z gh = get_gh_client(github_token) 2025-09-07T06:09:29.1162228Z 2025-09-07T06:09:29.1162486Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:29.1163083Z split_tag = ref_name.split("/") 2025-09-07T06:09:29.1163581Z if ( 2025-09-07T06:09:29.1163959Z len(split_tag) == 3 2025-09-07T06:09:29.1164430Z and split_tag[0] == "ciflow" 2025-09-07T06:09:29.1164948Z and split_tag[2].isnumeric() 2025-09-07T06:09:29.1165448Z ): 2025-09-07T06:09:29.1166263Z pr_number = split_tag[2] 2025-09-07T06:09:29.1166747Z try: 2025-09-07T06:09:29.1215593Z repository = gh.get_repo(repo) 2025-09-07T06:09:29.1216922Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:29.1217918Z except Exception as e: 2025-09-07T06:09:29.1218840Z raise Exception( # noqa: TRY002 2025-09-07T06:09:29.1219911Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:29.1220858Z ) from e 2025-09-07T06:09:29.1221409Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:29.1222115Z # In all other cases, return the original input username 2025-09-07T06:09:29.1222691Z return username 2025-09-07T06:09:29.1222940Z 2025-09-07T06:09:29.1222947Z 2025-09-07T06:09:29.1223165Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:29.1223693Z """ 2025-09-07T06:09:29.1224317Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:29.1225085Z """ 2025-09-07T06:09:29.1225624Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:29.1226433Z 2025-09-07T06:09:29.1226442Z 2025-09-07T06:09:29.1226637Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:29.1227146Z try: 2025-09-07T06:09:29.1227525Z data = yaml.safe_load(yaml_text) 2025-09-07T06:09:29.1228044Z return data 2025-09-07T06:09:29.1228453Z except yaml.YAMLError: 2025-09-07T06:09:29.1228932Z log.exception("Error loading YAML") 2025-09-07T06:09:29.1229457Z raise 2025-09-07T06:09:29.1229682Z 2025-09-07T06:09:29.1229689Z 2025-09-07T06:09:29.1230080Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:29.1230797Z """ 2025-09-07T06:09:29.1231392Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:29.1231982Z 2025-09-07T06:09:29.1232502Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:29.1233274Z and the text below is the list of opted in users. 2025-09-07T06:09:29.1233676Z 2025-09-07T06:09:29.1234034Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:29.1234718Z """ 2025-09-07T06:09:29.1235156Z rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:29.1235741Z if len(rollout_state_parts) >= 2: 2025-09-07T06:09:29.1236534Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:29.1237108Z else: 2025-09-07T06:09:29.1237488Z return "", rollout_state 2025-09-07T06:09:29.1237805Z 2025-09-07T06:09:29.1237813Z 2025-09-07T06:09:29.1238005Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:29.1238501Z """ 2025-09-07T06:09:29.1239007Z Dictionary of users with a list of features they have opted into 2025-09-07T06:09:29.1239639Z """ 2025-09-07T06:09:29.1239845Z 2025-09-07T06:09:29.1239852Z 2025-09-07T06:09:29.1240178Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:29.1240811Z """ 2025-09-07T06:09:29.1241516Z 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.1242177Z 2025-09-07T06:09:29.1242764Z 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.1243714Z - Example line: "@User1,lf,split_build" 2025-09-07T06:09:29.1244377Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:29.1244862Z 2025-09-07T06:09:29.1244868Z 2025-09-07T06:09:29.1245019Z """ 2025-09-07T06:09:29.1245383Z optins = UserOptins() 2025-09-07T06:09:29.1246042Z for user in user_optin_text.split("\n"): 2025-09-07T06:09:29.1246595Z user = user.strip("\r\n\t -") 2025-09-07T06:09:29.1247270Z if not user or not user.startswith("@"): 2025-09-07T06:09:29.1247821Z # Not a valid user. Skip 2025-09-07T06:09:29.1248294Z continue 2025-09-07T06:09:29.1248536Z 2025-09-07T06:09:29.1248698Z if user: 2025-09-07T06:09:29.1249150Z usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:29.1249827Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:29.1250300Z 2025-09-07T06:09:29.1250456Z return optins 2025-09-07T06:09:29.1250698Z 2025-09-07T06:09:29.1250705Z 2025-09-07T06:09:29.1250990Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:29.1251574Z """ 2025-09-07T06:09:29.1251958Z Check if the experiment name is valid. 2025-09-07T06:09:29.1252492Z A valid name: 2025-09-07T06:09:29.1253100Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:29.1253981Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:29.1254687Z - Cannot contain spaces 2025-09-07T06:09:29.1255297Z """ 2025-09-07T06:09:29.1255502Z 2025-09-07T06:09:29.1255759Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:29.1256643Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:29.1257077Z 2025-09-07T06:09:29.1257236Z if valid: 2025-09-07T06:09:29.1257606Z return True 2025-09-07T06:09:29.1257848Z 2025-09-07T06:09:29.1258000Z log.error( 2025-09-07T06:09:29.1259359Z 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.1260936Z ) 2025-09-07T06:09:29.1261288Z return False 2025-09-07T06:09:29.1261524Z 2025-09-07T06:09:29.1261530Z 2025-09-07T06:09:29.1261823Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:29.1262422Z """ 2025-09-07T06:09:29.1263178Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:29.1263874Z """ 2025-09-07T06:09:29.1264226Z try: 2025-09-07T06:09:29.1264586Z if settings_text: 2025-09-07T06:09:29.1265284Z # 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.1266247Z # for easy reading 2025-09-07T06:09:29.1267009Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:29.1267863Z # the backtick character in shell commands. 2025-09-07T06:09:29.1268445Z backtick = chr(96) # backtick character 2025-09-07T06:09:29.1269087Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:29.1269717Z settings = load_yaml(settings_text) 2025-09-07T06:09:29.1270103Z 2025-09-07T06:09:29.1270494Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:29.1271212Z experiments = {} 2025-09-07T06:09:29.1271509Z 2025-09-07T06:09:29.1271867Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:29.1272592Z if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:29.1273643Z # 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.1274654Z continue 2025-09-07T06:09:29.1274937Z 2025-09-07T06:09:29.1275113Z valid_settings = {} 2025-09-07T06:09:29.1275627Z for setting in exp_settings: 2025-09-07T06:09:29.1276781Z if setting not in Experiment._fields: 2025-09-07T06:09:29.1277325Z log.warning( 2025-09-07T06:09:29.1278013Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:29.1278864Z ) 2025-09-07T06:09:29.1279285Z else: 2025-09-07T06:09:29.1279790Z valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:29.1280207Z 2025-09-07T06:09:29.1280468Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:29.1281103Z return Settings(experiments) 2025-09-07T06:09:29.1281447Z 2025-09-07T06:09:29.1281612Z except Exception: 2025-09-07T06:09:29.1282076Z log.exception("Failed to parse settings") 2025-09-07T06:09:29.1282449Z 2025-09-07T06:09:29.1282609Z return Settings() 2025-09-07T06:09:29.1282867Z 2025-09-07T06:09:29.1282873Z 2025-09-07T06:09:29.1283109Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:29.1283646Z """ 2025-09-07T06:09:29.1284064Z Parse settings, if any, from the rollout state. 2025-09-07T06:09:29.1284454Z 2025-09-07T06:09:29.1284812Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:29.1285535Z and the text below is the list of opted in users. 2025-09-07T06:09:29.1286088Z 2025-09-07T06:09:29.1286483Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:29.1287196Z """ 2025-09-07T06:09:29.1287729Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:29.1288441Z return parse_settings_from_text(settings_text) 2025-09-07T06:09:29.1288835Z 2025-09-07T06:09:29.1288841Z 2025-09-07T06:09:29.1289074Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:29.1289605Z """ 2025-09-07T06:09:29.1289985Z Parse users from the rollout state. 2025-09-07T06:09:29.1290327Z 2025-09-07T06:09:29.1290480Z """ 2025-09-07T06:09:29.1290986Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:29.1291694Z return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:29.1292083Z 2025-09-07T06:09:29.1292090Z 2025-09-07T06:09:29.1292616Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:29.1293365Z """ 2025-09-07T06:09:29.1293773Z Check if a user is opted into an experiment 2025-09-07T06:09:29.1294334Z """ 2025-09-07T06:09:29.1294777Z return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:29.1295179Z 2025-09-07T06:09:29.1295185Z 2025-09-07T06:09:29.1295592Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:29.1296417Z """ 2025-09-07T06:09:29.1296895Z Check if a user explicitly opted out of an experiment 2025-09-07T06:09:29.1297447Z """ 2025-09-07T06:09:29.1297935Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:29.1298605Z experiment_optout = "-" + experiment_name 2025-09-07T06:09:29.1299221Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:29.1299803Z return False 2025-09-07T06:09:29.1300056Z 2025-09-07T06:09:29.1300309Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:29.1300884Z log.warning( 2025-09-07T06:09:29.1301641Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:29.1302474Z ) 2025-09-07T06:09:29.1302676Z 2025-09-07T06:09:29.1302832Z return True 2025-09-07T06:09:29.1303065Z 2025-09-07T06:09:29.1303071Z 2025-09-07T06:09:29.1303238Z def get_runner_prefix( 2025-09-07T06:09:29.1303671Z rollout_state: str, 2025-09-07T06:09:29.1304120Z workflow_requestors: Iterable[str], 2025-09-07T06:09:29.1304618Z branch: str, 2025-09-07T06:09:29.1305084Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:29.1305720Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:29.1306431Z is_canary: bool = False, 2025-09-07T06:09:29.1306872Z ) -> str: 2025-09-07T06:09:29.1307416Z settings = parse_settings(rollout_state) 2025-09-07T06:09:29.1307988Z user_optins = parse_users(rollout_state) 2025-09-07T06:09:29.1308353Z 2025-09-07T06:09:29.1308515Z fleet_prefix = "" 2025-09-07T06:09:29.1308946Z prefixes = [] 2025-09-07T06:09:29.1309725Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:29.1310633Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:29.1311314Z log.info( 2025-09-07T06:09:29.1311963Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:29.1312686Z ) 2025-09-07T06:09:29.1313054Z continue 2025-09-07T06:09:29.1313297Z 2025-09-07T06:09:29.1313474Z if opt_out_experiments: 2025-09-07T06:09:29.1313988Z if experiment_name in opt_out_experiments: 2025-09-07T06:09:29.1314590Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:29.1315155Z log.info( 2025-09-07T06:09:29.1316173Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:29.1317109Z ) 2025-09-07T06:09:29.1317500Z continue 2025-09-07T06:09:29.1317760Z 2025-09-07T06:09:29.1317937Z if eligible_experiments: 2025-09-07T06:09:29.1318470Z if experiment_name not in eligible_experiments: 2025-09-07T06:09:29.1319077Z exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:29.1319604Z log.info( 2025-09-07T06:09:29.1320341Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:29.1321138Z ) 2025-09-07T06:09:29.1321520Z continue 2025-09-07T06:09:29.1321974Z elif not experiment_settings.default: 2025-09-07T06:09:29.1322500Z log.info( 2025-09-07T06:09:29.1323269Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:29.1323980Z ) 2025-09-07T06:09:29.1324348Z continue 2025-09-07T06:09:29.1324602Z 2025-09-07T06:09:29.1324857Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:29.1325451Z opted_out_users = [ 2025-09-07T06:09:29.1326007Z requestor 2025-09-07T06:09:29.1326496Z for requestor in workflow_requestors 2025-09-07T06:09:29.1327137Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:29.1327744Z ] 2025-09-07T06:09:29.1327948Z 2025-09-07T06:09:29.1328122Z if opted_out_users: 2025-09-07T06:09:29.1328552Z log.info( 2025-09-07T06:09:29.1329146Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:29.1329798Z ) 2025-09-07T06:09:29.1330182Z continue 2025-09-07T06:09:29.1330426Z 2025-09-07T06:09:29.1330678Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:29.1331252Z opted_in_users = [ 2025-09-07T06:09:29.1331677Z requestor 2025-09-07T06:09:29.1332110Z for requestor in workflow_requestors 2025-09-07T06:09:29.1332739Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:29.1333330Z ] 2025-09-07T06:09:29.1333535Z 2025-09-07T06:09:29.1333696Z enabled = False 2025-09-07T06:09:29.1334113Z if opted_in_users: 2025-09-07T06:09:29.1334546Z log.info( 2025-09-07T06:09:29.1335120Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:29.1335772Z ) 2025-09-07T06:09:29.1336261Z enabled = True 2025-09-07T06:09:29.1336538Z 2025-09-07T06:09:29.1336744Z elif experiment_settings.rollout_perc: 2025-09-07T06:09:29.1337532Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:29.1338533Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:29.1339161Z log.info( 2025-09-07T06:09:29.1339972Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:29.1340848Z ) 2025-09-07T06:09:29.1341242Z enabled = True 2025-09-07T06:09:29.1341531Z 2025-09-07T06:09:29.1341686Z if enabled: 2025-09-07T06:09:29.1342098Z label = experiment_name 2025-09-07T06:09:29.1342629Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:29.1343411Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:29.1344233Z # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:29.1344968Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:29.1345597Z if is_canary: 2025-09-07T06:09:29.1346223Z label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:29.1346762Z fleet_prefix = label 2025-09-07T06:09:29.1347233Z else: 2025-09-07T06:09:29.1347651Z prefixes.append(label) 2025-09-07T06:09:29.1347992Z 2025-09-07T06:09:29.1348163Z if len(prefixes) > 1: 2025-09-07T06:09:29.1348590Z log.error( 2025-09-07T06:09:29.1349577Z 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.1350620Z ) 2025-09-07T06:09:29.1351001Z prefixes = prefixes[:1] 2025-09-07T06:09:29.1351300Z 2025-09-07T06:09:29.1351474Z # Fleet always comes first 2025-09-07T06:09:29.1351929Z if fleet_prefix: 2025-09-07T06:09:29.1352358Z prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:29.1352718Z 2025-09-07T06:09:29.1353096Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:29.1353501Z 2025-09-07T06:09:29.1353508Z 2025-09-07T06:09:29.1353914Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:29.1354652Z """ 2025-09-07T06:09:29.1355214Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:29.1355745Z 2025-09-07T06:09:29.1356232Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:29.1356912Z """ 2025-09-07T06:09:29.1357292Z gh = get_gh_client(github_token) 2025-09-07T06:09:29.1357813Z issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:29.1358418Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:29.1358848Z 2025-09-07T06:09:29.1358855Z 2025-09-07T06:09:29.1359226Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:29.1359969Z for _ in range(num_retries): 2025-09-07T06:09:29.1360429Z try: 2025-09-07T06:09:29.1360848Z req = Request(url=url, headers=headers) 2025-09-07T06:09:29.1361472Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:29.1362082Z return json.loads(content) 2025-09-07T06:09:29.1362581Z except Exception as e: 2025-09-07T06:09:29.1363094Z log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:29.1363478Z 2025-09-07T06:09:29.1363831Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:29.1364497Z return {} 2025-09-07T06:09:29.1364716Z 2025-09-07T06:09:29.1364722Z 2025-09-07T06:09:29.1364880Z @cache 2025-09-07T06:09:29.1365465Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:29.1366280Z """ 2025-09-07T06:09:29.1366659Z Dynamically get PR information 2025-09-07T06:09:29.1367271Z """ 2025-09-07T06:09:29.1367755Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:29.1368375Z headers = { 2025-09-07T06:09:29.1368816Z "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:29.1369388Z "Authorization": f"token {github_token}", 2025-09-07T06:09:29.1369902Z } 2025-09-07T06:09:29.1370312Z json_response: dict[str, Any] = download_json( 2025-09-07T06:09:29.1370884Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:29.1371408Z headers=headers, 2025-09-07T06:09:29.1371819Z ) 2025-09-07T06:09:29.1372016Z 2025-09-07T06:09:29.1372185Z if not json_response: 2025-09-07T06:09:29.1372731Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:29.1373317Z return {} 2025-09-07T06:09:29.1373545Z 2025-09-07T06:09:29.1373718Z return json_response 2025-09-07T06:09:29.1373991Z 2025-09-07T06:09:29.1374004Z 2025-09-07T06:09:29.1374371Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:29.1375060Z """ 2025-09-07T06:09:29.1375560Z Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:29.1376278Z """ 2025-09-07T06:09:29.1376739Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:29.1377317Z return { 2025-09-07T06:09:29.1377862Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:29.1378519Z } 2025-09-07T06:09:29.1378722Z 2025-09-07T06:09:29.1378728Z 2025-09-07T06:09:29.1378892Z def main() -> None: 2025-09-07T06:09:29.1379303Z args = parse_args() 2025-09-07T06:09:29.1379561Z 2025-09-07T06:09:29.1379767Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:29.1380144Z 2025-09-07T06:09:29.1380325Z # Check if the PR is opt-out 2025-09-07T06:09:29.1380797Z if args.pr_number: 2025-09-07T06:09:29.1381415Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:29.1382270Z if OPT_OUT_LABEL in labels: 2025-09-07T06:09:29.1382752Z log.info( 2025-09-07T06:09:29.1383402Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:29.1384123Z ) 2025-09-07T06:09:29.1384646Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:29.1385276Z sys.exit() 2025-09-07T06:09:29.1385535Z 2025-09-07T06:09:29.1385689Z try: 2025-09-07T06:09:29.1386266Z rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:29.1386939Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:29.1387541Z ) 2025-09-07T06:09:29.1387745Z 2025-09-07T06:09:29.1387935Z username = get_potential_pr_author( 2025-09-07T06:09:29.1388456Z args.github_token, 2025-09-07T06:09:29.1388906Z args.github_repo, 2025-09-07T06:09:29.1389368Z args.github_actor, 2025-09-07T06:09:29.1389837Z args.github_ref_type, 2025-09-07T06:09:29.1390318Z args.github_branch, 2025-09-07T06:09:29.1390759Z ) 2025-09-07T06:09:29.1390965Z 2025-09-07T06:09:29.1391229Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:29.1391659Z 2025-09-07T06:09:29.1391869Z runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:29.1392398Z rollout_state, 2025-09-07T06:09:29.1392864Z (args.github_issue_owner, username), 2025-09-07T06:09:29.1393379Z args.github_branch, 2025-09-07T06:09:29.1393851Z args.eligible_experiments, 2025-09-07T06:09:29.1394362Z args.opt_out_experiments, 2025-09-07T06:09:29.1394844Z is_canary, 2025-09-07T06:09:29.1395243Z ) 2025-09-07T06:09:29.1395446Z 2025-09-07T06:09:29.1395616Z except Exception as e: 2025-09-07T06:09:29.1396182Z log.error( 2025-09-07T06:09:29.1396807Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:29.1397664Z ) 2025-09-07T06:09:29.1397867Z 2025-09-07T06:09:29.1398168Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:29.1398644Z 2025-09-07T06:09:29.1398649Z 2025-09-07T06:09:29.1398814Z if __name__ == "__main__": 2025-09-07T06:09:29.1399236Z main() 2025-09-07T06:09:29.1399445Z 2025-09-07T06:09:29.1488883Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:29.1489710Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:29.1520547Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:29.1520998Z env: 2025-09-07T06:09:29.1521605Z GITHUB_TOKEN: *** 2025-09-07T06:09:29.1522001Z ISSUE_NUMBER: 5132 2025-09-07T06:09:29.1522407Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:29.1522868Z ISSUE_OWNER: 2025-09-07T06:09:29.1523238Z CHECK_EXPERIMENTS: 2025-09-07T06:09:29.1523624Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:29.1524023Z PR_NUMBER: 2025-09-07T06:09:29.1524360Z ##[endgroup] 2025-09-07T06:09:32.0262817Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T06:09:33.3781138Z Collecting urllib3==1.26.18 2025-09-07T06:09:33.4161910Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T06:09:33.4387938Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.4 MB/s eta 0:00:00 2025-09-07T06:09:33.4614493Z Collecting PyGithub==2.3.0 2025-09-07T06:09:33.4709086Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T06:09:33.5138827Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T06:09:33.5166929Z 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:33.5212020Z 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:33.5224281Z 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:33.5244008Z 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:33.5501852Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T06:09:33.5533138Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T06:09:33.5827914Z 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:33.7090515Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:33.7123246Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T06:09:33.8335395Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T06:09:33.8369200Z 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:33.8552822Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:33.8582299Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T06:09:33.8813323Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T06:09:33.8881921Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 28.7 MB/s eta 0:00:00 2025-09-07T06:09:33.8926304Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T06:09:33.8990244Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 80.0 MB/s eta 0:00:00 2025-09-07T06:09:33.9031786Z 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:33.9120756Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 122.7 MB/s eta 0:00:00 2025-09-07T06:09:33.9147904Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T06:09:33.9198912Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T06:09:33.9263255Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 102.8 MB/s eta 0:00:00 2025-09-07T06:09:33.9291798Z 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:33.9335244Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 28.1 MB/s eta 0:00:00 2025-09-07T06:09:33.9361579Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T06:09:33.9405400Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 39.0 MB/s eta 0:00:00 2025-09-07T06:09:34.3224154Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T06:09:34.8676088Z 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:34.9714753Z ##[group]Run curr_branch="main" 2025-09-07T06:09:34.9715058Z curr_branch="main" 2025-09-07T06:09:34.9715318Z curr_ref_type="branch" 2025-09-07T06:09:34.9715565Z echo "Current branch is '$curr_branch'" 2025-09-07T06:09:34.9715992Z  2025-09-07T06:09:34.9716180Z python3 runner_determinator.py \ 2025-09-07T06:09:34.9716467Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T06:09:34.9716737Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T06:09:34.9716982Z  --github-branch "$curr_branch" \ 2025-09-07T06:09:34.9717251Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T06:09:34.9717540Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T06:09:34.9717827Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T06:09:34.9718097Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T06:09:34.9718413Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T06:09:34.9718779Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T06:09:34.9719062Z  --pr-number "${PR_NUMBER}" 2025-09-07T06:09:34.9749965Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:34.9750198Z env: 2025-09-07T06:09:34.9750692Z GITHUB_TOKEN: *** 2025-09-07T06:09:34.9750891Z ISSUE_NUMBER: 5132 2025-09-07T06:09:34.9751116Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:34.9751358Z ISSUE_OWNER: 2025-09-07T06:09:34.9751541Z CHECK_EXPERIMENTS: 2025-09-07T06:09:34.9751728Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:34.9751926Z PR_NUMBER: 2025-09-07T06:09:34.9752091Z ##[endgroup] 2025-09-07T06:09:34.9803223Z Current branch is 'main' 2025-09-07T06:09:36.7594790Z INFO : Based on rollout percentage of 60%, enabling experiment lf. 2025-09-07T06:09:36.7596559Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T06:09:36.7597794Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T06:09:36.7598944Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T06:09:36.7599510Z INFO : Setting output: label-type='lf.' 2025-09-07T06:09:36.7955241Z Evaluate and set job outputs 2025-09-07T06:09:36.7962480Z Set output 'label-type' 2025-09-07T06:09:36.7964288Z Cleaning up orphan processes