2025-07-17T06:33:19.8656778Z Current runner version: '2.326.0' 2025-07-17T06:33:19.8689774Z ##[group]Runner Image Provisioner 2025-07-17T06:33:19.8691010Z Hosted Compute Agent 2025-07-17T06:33:19.8691908Z Version: 20250711.363 2025-07-17T06:33:19.8692757Z Commit: 6785254374ce925a23743850c1cb91912ce5c14c 2025-07-17T06:33:19.8694203Z Build Date: 2025-07-11T20:04:25Z 2025-07-17T06:33:19.8695200Z ##[endgroup] 2025-07-17T06:33:19.8696117Z ##[group]Operating System 2025-07-17T06:33:19.8697179Z Ubuntu 2025-07-17T06:33:19.8697925Z 24.04.2 2025-07-17T06:33:19.8698813Z LTS 2025-07-17T06:33:19.8699666Z ##[endgroup] 2025-07-17T06:33:19.8700542Z ##[group]Runner Image 2025-07-17T06:33:19.8701445Z Image: ubuntu-24.04 2025-07-17T06:33:19.8702473Z Version: 20250710.1.0 2025-07-17T06:33:19.8704499Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250710.1/images/ubuntu/Ubuntu2404-Readme.md 2025-07-17T06:33:19.8707230Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250710.1 2025-07-17T06:33:19.8708953Z ##[endgroup] 2025-07-17T06:33:19.8710805Z ##[group]GITHUB_TOKEN Permissions 2025-07-17T06:33:19.8713781Z Contents: read 2025-07-17T06:33:19.8714801Z Metadata: read 2025-07-17T06:33:19.8715677Z ##[endgroup] 2025-07-17T06:33:19.8718410Z Secret source: Actions 2025-07-17T06:33:19.8719480Z Prepare workflow directory 2025-07-17T06:33:19.9367116Z Prepare all required actions 2025-07-17T06:33:19.9448062Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (a38f433be2e94a64b095a44ba39879d02d0c2316) 2025-07-17T06:33:19.9455950Z ##[group] Inputs 2025-07-17T06:33:19.9456852Z check_experiments: 2025-07-17T06:33:19.9457727Z opt_out_experiments: 2025-07-17T06:33:19.9458743Z triggering_actor: pytorchmergebot 2025-07-17T06:33:19.9459698Z issue_owner: 2025-07-17T06:33:19.9460521Z curr_branch: main 2025-07-17T06:33:19.9461416Z curr_ref_type: branch 2025-07-17T06:33:19.9462407Z issue_number: 5132 2025-07-17T06:33:19.9463564Z ##[endgroup] 2025-07-17T06:33:19.9464687Z Complete job name: before-test / get-label-type / runner-determinator 2025-07-17T06:33:20.5249016Z ##[group]Run cat < runner_determinator.py 2025-07-17T06:33:20.5251422Z cat < runner_determinator.py 2025-07-17T06:33:20.5252191Z # flake8: noqa: G004 2025-07-17T06:33:20.5252742Z  2025-07-17T06:33:20.5253731Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:33:20.5254940Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:33:20.5255907Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:33:20.5256657Z  2025-07-17T06:33:20.5257128Z """ 2025-07-17T06:33:20.5257895Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:33:20.5258919Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:33:20.5260216Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:33:20.5261199Z of which runners should be used to run which job. 2025-07-17T06:33:20.5261890Z  2025-07-17T06:33:20.5262697Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:33:20.5264090Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:33:20.5265126Z settings are considered to be empty with only the second part, the user 2025-07-17T06:33:20.5266022Z list, defined. 2025-07-17T06:33:20.5266602Z  2025-07-17T06:33:20.5267290Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:33:20.5268415Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:33:20.5269435Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:33:20.5270144Z  2025-07-17T06:33:20.5271213Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:33:20.5272269Z The user list is also a comma separated list of additional features or 2025-07-17T06:33:20.5273149Z experiments which the user could be opted in to. 2025-07-17T06:33:20.5274301Z  2025-07-17T06:33:20.5274825Z The user list has the following rules: 2025-07-17T06:33:20.5275548Z  2025-07-17T06:33:20.5276271Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:33:20.5277338Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:33:20.5278270Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:33:20.5278962Z  2025-07-17T06:33:20.5279463Z Example config: 2025-07-17T06:33:20.5280055Z  # A list of experiments that can be opted into. 2025-07-17T06:33:20.5280955Z  # This defines the behavior they'll induce when opted into. 2025-07-17T06:33:20.5281771Z  # Expected syntax is: 2025-07-17T06:33:20.5282571Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:33:20.5283879Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:33:20.5284768Z  2025-07-17T06:33:20.5285249Z  experiments: 2025-07-17T06:33:20.5361882Z  lf: 2025-07-17T06:33:20.5362482Z  rollout_percent: 25 2025-07-17T06:33:20.5363052Z  all_branches: false 2025-07-17T06:33:20.5363811Z  default: true 2025-07-17T06:33:20.5364293Z  --- 2025-07-17T06:33:20.5364691Z  2025-07-17T06:33:20.5365078Z  # Opt-ins: 2025-07-17T06:33:20.5365757Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:33:20.5367025Z  # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:33:20.5367939Z  # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:33:20.5368700Z  # Experiments should be from the above list. 2025-07-17T06:33:20.5369297Z  2025-07-17T06:33:20.5369704Z  @User1,-lf,split_build 2025-07-17T06:33:20.5370226Z  @User2,lf 2025-07-17T06:33:20.5370683Z  @User3,split_build 2025-07-17T06:33:20.5371160Z """ 2025-07-17T06:33:20.5371546Z  2025-07-17T06:33:20.5371926Z import json 2025-07-17T06:33:20.5372363Z import logging 2025-07-17T06:33:20.5372803Z import os 2025-07-17T06:33:20.5373235Z import random 2025-07-17T06:33:20.5373794Z import re 2025-07-17T06:33:20.5374209Z import sys 2025-07-17T06:33:20.5374680Z from argparse import ArgumentParser 2025-07-17T06:33:20.5375356Z from collections.abc import Iterable 2025-07-17T06:33:20.5375953Z from functools import cache 2025-07-17T06:33:20.5376488Z from logging import LogRecord 2025-07-17T06:33:20.5377055Z from typing import Any, NamedTuple 2025-07-17T06:33:20.5377677Z from urllib.request import Request, urlopen 2025-07-17T06:33:20.5378279Z  2025-07-17T06:33:20.5378668Z import yaml 2025-07-17T06:33:20.5379133Z from github import Auth, Github 2025-07-17T06:33:20.5380042Z from github.Issue import Issue 2025-07-17T06:33:20.5380564Z  2025-07-17T06:33:20.5380943Z  2025-07-17T06:33:20.5381413Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:33:20.5382193Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:33:20.5383176Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:33:20.5384073Z  2025-07-17T06:33:20.5384837Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:33:20.5385477Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:33:20.5386081Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:33:20.5386742Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:33:20.5387312Z  2025-07-17T06:33:20.5387747Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:33:20.5388308Z  2025-07-17T06:33:20.5388714Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:33:20.5389244Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:33:20.5389740Z  2025-07-17T06:33:20.5390109Z  2025-07-17T06:33:20.5390528Z class Experiment(NamedTuple): 2025-07-17T06:33:20.5391075Z  rollout_perc: float = ( 2025-07-17T06:33:20.5391822Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:33:20.5392561Z  ) 2025-07-17T06:33:20.5392980Z  all_branches: bool = ( 2025-07-17T06:33:20.5394027Z  False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:33:20.5394798Z  ) 2025-07-17T06:33:20.5395218Z  default: bool = ( 2025-07-17T06:33:20.5395871Z  True # If True, the experiment is enabled by default for all queries 2025-07-17T06:33:20.5396567Z  ) 2025-07-17T06:33:20.5396957Z  2025-07-17T06:33:20.5397364Z  # Add more fields as needed 2025-07-17T06:33:20.5397884Z  2025-07-17T06:33:20.5398255Z  2025-07-17T06:33:20.5398660Z class Settings(NamedTuple): 2025-07-17T06:33:20.5399168Z  """ 2025-07-17T06:33:20.5399705Z  Settings for the experiments that can be opted into. 2025-07-17T06:33:20.5400331Z  """ 2025-07-17T06:33:20.5400727Z  2025-07-17T06:33:20.5401161Z  experiments: dict[str, Experiment] = {} 2025-07-17T06:33:20.5401731Z  2025-07-17T06:33:20.5402248Z  2025-07-17T06:33:20.5402703Z class ColorFormatter(logging.Formatter): 2025-07-17T06:33:20.5403778Z  """Color codes the log messages based on the log level""" 2025-07-17T06:33:20.5404436Z  2025-07-17T06:33:20.5404829Z  COLORS = { 2025-07-17T06:33:20.5405305Z  "WARNING": "\033[33m", # Yellow 2025-07-17T06:33:20.5405870Z  "ERROR": "\033[31m", # Red 2025-07-17T06:33:20.5406429Z  "CRITICAL": "\033[31m", # Red 2025-07-17T06:33:20.5406989Z  "INFO": "\033[0m", # Reset 2025-07-17T06:33:20.5407542Z  "DEBUG": "\033[0m", # Reset 2025-07-17T06:33:20.5408061Z  } 2025-07-17T06:33:20.5408452Z  2025-07-17T06:33:20.5408900Z  def format(self, record: LogRecord) -> str: 2025-07-17T06:33:20.5409719Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:33:20.5410578Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:33:20.5411211Z  return super().format(record) 2025-07-17T06:33:20.5411744Z  2025-07-17T06:33:20.5412109Z  2025-07-17T06:33:20.5412534Z handler = logging.StreamHandler() 2025-07-17T06:33:20.5413443Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:33:20.5414219Z  2025-07-17T06:33:20.5414717Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:33:20.5415359Z log.addHandler(handler) 2025-07-17T06:33:20.5415875Z log.setLevel(logging.INFO) 2025-07-17T06:33:20.5416373Z  2025-07-17T06:33:20.5416740Z  2025-07-17T06:33:20.5417239Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:33:20.5417873Z  """ 2025-07-17T06:33:20.5418455Z  Defines outputs of the github action that invokes this script 2025-07-17T06:33:20.5419304Z  """ 2025-07-17T06:33:20.5419725Z  if not GITHUB_OUTPUT: 2025-07-17T06:33:20.5420884Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:33:20.5422107Z  log.warning( 2025-07-17T06:33:20.5423079Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-07-17T06:33:20.5424221Z  ) 2025-07-17T06:33:20.5424728Z  print(f"::set-output name={key}::{value}") 2025-07-17T06:33:20.5425328Z  return 2025-07-17T06:33:20.5425761Z  2025-07-17T06:33:20.5426191Z  with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:33:20.5426825Z  log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:33:20.5427445Z  f.write(f"{key}={value}\n") 2025-07-17T06:33:20.5427979Z  2025-07-17T06:33:20.5428350Z  2025-07-17T06:33:20.5428905Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:33:20.5429611Z  return frozenset( 2025-07-17T06:33:20.5430315Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:33:20.5431049Z  ) 2025-07-17T06:33:20.5431453Z  2025-07-17T06:33:20.5431827Z  2025-07-17T06:33:20.5432223Z def parse_args() -> Any: 2025-07-17T06:33:20.5432899Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:33:20.5434136Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:33:20.5434983Z  parser.add_argument( 2025-07-17T06:33:20.5435521Z  "--github-issue-repo", 2025-07-17T06:33:20.5436051Z  type=str, 2025-07-17T06:33:20.5436546Z  required=False, 2025-07-17T06:33:20.5437223Z  default="pytorch/test-infra", 2025-07-17T06:33:20.5437912Z  help="GitHub repo to get the issue", 2025-07-17T06:33:20.5438469Z  ) 2025-07-17T06:33:20.5438893Z  parser.add_argument( 2025-07-17T06:33:20.5439405Z  "--github-repo", 2025-07-17T06:33:20.5439909Z  type=str, 2025-07-17T06:33:20.5440382Z  required=True, 2025-07-17T06:33:20.5440925Z  help="GitHub repo where CI is running", 2025-07-17T06:33:20.5441492Z  ) 2025-07-17T06:33:20.5441909Z  parser.add_argument( 2025-07-17T06:33:20.5442612Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:33:20.5443522Z  ) 2025-07-17T06:33:20.5444009Z  parser.add_argument( 2025-07-17T06:33:20.5444750Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:33:20.5445506Z  ) 2025-07-17T06:33:20.5445931Z  parser.add_argument( 2025-07-17T06:33:20.5446663Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:33:20.5447406Z  ) 2025-07-17T06:33:20.5447824Z  parser.add_argument( 2025-07-17T06:33:20.5448589Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:33:20.5449363Z  ) 2025-07-17T06:33:20.5449814Z  parser.add_argument( 2025-07-17T06:33:20.5450342Z  "--github-ref-type", 2025-07-17T06:33:20.5450867Z  type=str, 2025-07-17T06:33:20.5451341Z  required=True, 2025-07-17T06:33:20.5451930Z  help="Current GitHub ref type, branch or tag", 2025-07-17T06:33:20.5452530Z  ) 2025-07-17T06:33:20.5452950Z  parser.add_argument( 2025-07-17T06:33:20.5453772Z  "--eligible-experiments", 2025-07-17T06:33:20.5454371Z  type=_str_comma_separated_to_set, 2025-07-17T06:33:20.5454945Z  required=False, 2025-07-17T06:33:20.5455440Z  default="", 2025-07-17T06:33:20.5456392Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:33:20.5457377Z  ) 2025-07-17T06:33:20.5457799Z  parser.add_argument( 2025-07-17T06:33:20.5458335Z  "--opt-out-experiments", 2025-07-17T06:33:20.5458913Z  type=_str_comma_separated_to_set, 2025-07-17T06:33:20.5459485Z  required=False, 2025-07-17T06:33:20.5459981Z  default="", 2025-07-17T06:33:20.5460456Z  help=( 2025-07-17T06:33:20.5461216Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:33:20.5462452Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:33:20.5463451Z  ), 2025-07-17T06:33:20.5463869Z  ) 2025-07-17T06:33:20.5464295Z  parser.add_argument( 2025-07-17T06:33:20.5464804Z  "--pr-number", 2025-07-17T06:33:20.5465305Z  type=str, 2025-07-17T06:33:20.5465784Z  required=False, 2025-07-17T06:33:20.5466285Z  default="", 2025-07-17T06:33:20.5466855Z  help="the optional PR number where this is run", 2025-07-17T06:33:20.5467471Z  ) 2025-07-17T06:33:20.5467868Z  2025-07-17T06:33:20.5468281Z  return parser.parse_args() 2025-07-17T06:33:20.5468812Z  2025-07-17T06:33:20.5469180Z  2025-07-17T06:33:20.5469836Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:33:20.5470800Z  auth = Auth.Token(github_token) 2025-07-17T06:33:20.5471408Z  return Github(auth=auth) 2025-07-17T06:33:20.5471916Z  2025-07-17T06:33:20.5472288Z  2025-07-17T06:33:20.5473001Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:33:20.5474165Z  repo = gh.get_repo(repo) 2025-07-17T06:33:20.5474768Z  return repo.get_issue(number=issue_num) 2025-07-17T06:33:20.5475351Z  2025-07-17T06:33:20.5475731Z  2025-07-17T06:33:20.5476141Z def get_potential_pr_author( 2025-07-17T06:33:20.5476888Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:33:20.5477634Z ) -> str: 2025-07-17T06:33:20.5478241Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:33:20.5479140Z  # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:33:20.5479991Z  # embedded in the tag name: ciflow// 2025-07-17T06:33:20.5480624Z  2025-07-17T06:33:20.5481058Z  gh = get_gh_client(github_token) 2025-07-17T06:33:20.5481597Z  2025-07-17T06:33:20.5482118Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:33:20.5482818Z  split_tag = ref_name.split("/") 2025-07-17T06:33:20.5484008Z  if ( 2025-07-17T06:33:20.5484510Z  len(split_tag) == 3 2025-07-17T06:33:20.5485086Z  and split_tag[0] == "ciflow" 2025-07-17T06:33:20.5485690Z  and split_tag[2].isnumeric() 2025-07-17T06:33:20.5486241Z  ): 2025-07-17T06:33:20.5486703Z  pr_number = split_tag[2] 2025-07-17T06:33:20.5487250Z  try: 2025-07-17T06:33:20.5487761Z  repository = gh.get_repo(repo) 2025-07-17T06:33:20.5488618Z  pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:33:20.5489291Z  except Exception as e: 2025-07-17T06:33:20.5489875Z  raise Exception( # noqa: TRY002 2025-07-17T06:33:20.5490611Z  f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:33:20.5491317Z  ) from e 2025-07-17T06:33:20.5491945Z  return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:33:20.5492728Z  # In all other cases, return the original input username 2025-07-17T06:33:20.5493491Z  return username 2025-07-17T06:33:20.5493959Z  2025-07-17T06:33:20.5494324Z  2025-07-17T06:33:20.5494791Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:33:20.5495372Z  """ 2025-07-17T06:33:20.5496100Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:33:20.5496951Z  """ 2025-07-17T06:33:20.5497556Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:33:20.5498282Z  2025-07-17T06:33:20.5498646Z  2025-07-17T06:33:20.5499073Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:33:20.5499620Z  try: 2025-07-17T06:33:20.5500071Z  data = yaml.safe_load(yaml_text) 2025-07-17T06:33:20.5500642Z  return data 2025-07-17T06:33:20.5501134Z  except yaml.YAMLError: 2025-07-17T06:33:20.5501700Z  log.exception("Error loading YAML") 2025-07-17T06:33:20.5502255Z  raise 2025-07-17T06:33:20.5502682Z  2025-07-17T06:33:20.5503045Z  2025-07-17T06:33:20.5503817Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:33:20.5504616Z  """ 2025-07-17T06:33:20.5505434Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:33:20.5506257Z  2025-07-17T06:33:20.5506844Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:20.5507691Z  and the text below is the list of opted in users. 2025-07-17T06:33:20.5508290Z  2025-07-17T06:33:20.5508916Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:33:20.5509680Z  """ 2025-07-17T06:33:20.5510192Z  rollout_state_parts = rollout_state.split("---") 2025-07-17T06:33:20.5510849Z  if len(rollout_state_parts) >= 2: 2025-07-17T06:33:20.5511527Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:33:20.5512417Z  else: 2025-07-17T06:33:20.5512862Z  return "", rollout_state 2025-07-17T06:33:20.5513610Z  2025-07-17T06:33:20.5514049Z  2025-07-17T06:33:20.5514496Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:33:20.5515060Z  """ 2025-07-17T06:33:20.5515649Z  Dictionary of users with a list of features they have opted into 2025-07-17T06:33:20.5516350Z  """ 2025-07-17T06:33:20.5516734Z  2025-07-17T06:33:20.5517094Z  2025-07-17T06:33:20.5517663Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:33:20.5518369Z  """ 2025-07-17T06:33:20.5519150Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-07-17T06:33:20.5520029Z  2025-07-17T06:33:20.5520895Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:33:20.5521955Z  - Example line: "@User1,lf,split_build" 2025-07-17T06:33:20.5522854Z  - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:33:20.5523779Z  2025-07-17T06:33:20.5524171Z  2025-07-17T06:33:20.5524535Z  """ 2025-07-17T06:33:20.5524945Z  optins = UserOptins() 2025-07-17T06:33:20.5525512Z  for user in user_optin_text.split("\n"): 2025-07-17T06:33:20.5526122Z  user = user.strip("\r\n\t -") 2025-07-17T06:33:20.5526737Z  if not user or not user.startswith("@"): 2025-07-17T06:33:20.5527338Z  # Not a valid user. Skip 2025-07-17T06:33:20.5527880Z  continue 2025-07-17T06:33:20.5528364Z  2025-07-17T06:33:20.5528740Z  if user: 2025-07-17T06:33:20.5529256Z  usr_name = user.split(",")[0].strip("@") 2025-07-17T06:33:20.5530010Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:33:20.5530700Z  2025-07-17T06:33:20.5531084Z  return optins 2025-07-17T06:33:20.5531527Z  2025-07-17T06:33:20.5531879Z  2025-07-17T06:33:20.5532414Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:33:20.5533075Z  """ 2025-07-17T06:33:20.5533637Z  Check if the experiment name is valid. 2025-07-17T06:33:20.5534210Z  A valid name: 2025-07-17T06:33:20.5534925Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:33:20.5535930Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:33:20.5536692Z  - Cannot contain spaces 2025-07-17T06:33:20.5537211Z  """ 2025-07-17T06:33:20.5537599Z  2025-07-17T06:33:20.5538087Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:33:20.5538865Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:33:20.5539739Z  2025-07-17T06:33:20.5540316Z  if valid: 2025-07-17T06:33:20.5540786Z  return True 2025-07-17T06:33:20.5541230Z  2025-07-17T06:33:20.5541600Z  log.error( 2025-07-17T06:33:20.5543160Z  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-07-17T06:33:20.5544918Z  ) 2025-07-17T06:33:20.5545313Z  return False 2025-07-17T06:33:20.5545762Z  2025-07-17T06:33:20.5546118Z  2025-07-17T06:33:20.5546670Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:33:20.5547354Z  """ 2025-07-17T06:33:20.5548015Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:33:20.5548798Z  """ 2025-07-17T06:33:20.5549191Z  try: 2025-07-17T06:33:20.5549607Z  if settings_text: 2025-07-17T06:33:20.5550412Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:33:20.5551255Z  # for easy reading 2025-07-17T06:33:20.5552142Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:33:20.5553098Z  # the backtick character in shell commands. 2025-07-17T06:33:20.5554056Z  backtick = chr(96) # backtick character 2025-07-17T06:33:20.5554810Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:33:20.5555537Z  settings = load_yaml(settings_text) 2025-07-17T06:33:20.5556094Z  2025-07-17T06:33:20.5556745Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:33:20.5557707Z  experiments = {} 2025-07-17T06:33:20.5558204Z  2025-07-17T06:33:20.5558806Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:33:20.5559629Z  if not is_valid_experiment_name(exp_name): 2025-07-17T06:33:20.5560816Z  # 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-07-17T06:33:20.5561944Z  continue 2025-07-17T06:33:20.5562435Z  2025-07-17T06:33:20.5562846Z  valid_settings = {} 2025-07-17T06:33:20.5563664Z  for setting in exp_settings: 2025-07-17T06:33:20.5564313Z  if setting not in Experiment._fields: 2025-07-17T06:33:20.5564924Z  log.warning( 2025-07-17T06:33:20.5565730Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:33:20.5566495Z  ) 2025-07-17T06:33:20.5566990Z  else: 2025-07-17T06:33:20.5567593Z  valid_settings[setting] = exp_settings[setting] 2025-07-17T06:33:20.5568201Z  2025-07-17T06:33:20.5568726Z  experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:33:20.5569427Z  return Settings(experiments) 2025-07-17T06:33:20.5569970Z  2025-07-17T06:33:20.5570361Z  except Exception: 2025-07-17T06:33:20.5570922Z  log.exception("Failed to parse settings") 2025-07-17T06:33:20.5571504Z  2025-07-17T06:33:20.5571891Z  return Settings() 2025-07-17T06:33:20.5572358Z  2025-07-17T06:33:20.5572724Z  2025-07-17T06:33:20.5573554Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:33:20.5574235Z  """ 2025-07-17T06:33:20.5574738Z  Parse settings, if any, from the rollout state. 2025-07-17T06:33:20.5575337Z  2025-07-17T06:33:20.5575925Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:20.5576748Z  and the text below is the list of opted in users. 2025-07-17T06:33:20.5577341Z  2025-07-17T06:33:20.5577977Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:33:20.5578758Z  """ 2025-07-17T06:33:20.5579377Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:20.5580210Z  return parse_settings_from_text(settings_text) 2025-07-17T06:33:20.5580791Z  2025-07-17T06:33:20.5581153Z  2025-07-17T06:33:20.5581651Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:33:20.5582270Z  """ 2025-07-17T06:33:20.5582717Z  Parse users from the rollout state. 2025-07-17T06:33:20.5583263Z  2025-07-17T06:33:20.5583739Z  """ 2025-07-17T06:33:20.5584338Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:20.5585149Z  return parse_user_opt_in_from_text(users_text) 2025-07-17T06:33:20.5585727Z  2025-07-17T06:33:20.5586089Z  2025-07-17T06:33:20.5586751Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:20.5587537Z  """ 2025-07-17T06:33:20.5588008Z  Check if a user is opted into an experiment 2025-07-17T06:33:20.5588580Z  """ 2025-07-17T06:33:20.5589105Z  return experiment_name in user_optins.get(user, []) 2025-07-17T06:33:20.5589718Z  2025-07-17T06:33:20.5590218Z  2025-07-17T06:33:20.5590888Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:20.5591699Z  """ 2025-07-17T06:33:20.5592209Z  Check if a user explicitly opted out of an experiment 2025-07-17T06:33:20.5592837Z  """ 2025-07-17T06:33:20.5593503Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:33:20.5594267Z  experiment_optout = "-" + experiment_name 2025-07-17T06:33:20.5594979Z  if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:33:20.5595628Z  return False 2025-07-17T06:33:20.5596090Z  2025-07-17T06:33:20.5596585Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:33:20.5597237Z  log.warning( 2025-07-17T06:33:20.5598148Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:33:20.5599081Z  ) 2025-07-17T06:33:20.5599486Z  2025-07-17T06:33:20.5599858Z  return True 2025-07-17T06:33:20.5600295Z  2025-07-17T06:33:20.5600655Z  2025-07-17T06:33:20.5601052Z def get_runner_prefix( 2025-07-17T06:33:20.5601554Z  rollout_state: str, 2025-07-17T06:33:20.5602092Z  workflow_requestors: Iterable[str], 2025-07-17T06:33:20.5602656Z  branch: str, 2025-07-17T06:33:20.5603234Z  eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:20.5604088Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:20.5604722Z  is_canary: bool = False, 2025-07-17T06:33:20.5605229Z ) -> str: 2025-07-17T06:33:20.5605710Z  settings = parse_settings(rollout_state) 2025-07-17T06:33:20.5606348Z  user_optins = parse_users(rollout_state) 2025-07-17T06:33:20.5606915Z  2025-07-17T06:33:20.5607415Z  fleet_prefix = "" 2025-07-17T06:33:20.5607918Z  prefixes = [] 2025-07-17T06:33:20.5608640Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:33:20.5609657Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:33:20.5610421Z  log.info( 2025-07-17T06:33:20.5611182Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:33:20.5611984Z  ) 2025-07-17T06:33:20.5612471Z  continue 2025-07-17T06:33:20.5612930Z  2025-07-17T06:33:20.5613432Z  if opt_out_experiments: 2025-07-17T06:33:20.5614048Z  if experiment_name in opt_out_experiments: 2025-07-17T06:33:20.5614760Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:33:20.5615412Z  log.info( 2025-07-17T06:33:20.5616417Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:33:20.5617444Z  ) 2025-07-17T06:33:20.5617907Z  continue 2025-07-17T06:33:20.5618372Z  2025-07-17T06:33:20.5618774Z  if eligible_experiments: 2025-07-17T06:33:20.5619399Z  if experiment_name not in eligible_experiments: 2025-07-17T06:33:20.5620095Z  exp_list = ", ".join(eligible_experiments) 2025-07-17T06:33:20.5620692Z  log.info( 2025-07-17T06:33:20.5621551Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:33:20.5622447Z  ) 2025-07-17T06:33:20.5623048Z  continue 2025-07-17T06:33:20.5623720Z  elif not experiment_settings.default: 2025-07-17T06:33:20.5624336Z  log.info( 2025-07-17T06:33:20.5625068Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:33:20.5625851Z  ) 2025-07-17T06:33:20.5626284Z  continue 2025-07-17T06:33:20.5626737Z  2025-07-17T06:33:20.5627241Z  # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:33:20.5627905Z  opted_out_users = [ 2025-07-17T06:33:20.5628416Z  requestor 2025-07-17T06:33:20.5628946Z  for requestor in workflow_requestors 2025-07-17T06:33:20.5629683Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:33:20.5630356Z  ] 2025-07-17T06:33:20.5630756Z  2025-07-17T06:33:20.5631155Z  if opted_out_users: 2025-07-17T06:33:20.5631690Z  log.info( 2025-07-17T06:33:20.5632397Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:33:20.5633152Z  ) 2025-07-17T06:33:20.5633690Z  continue 2025-07-17T06:33:20.5634144Z  2025-07-17T06:33:20.5634649Z  # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:33:20.5635304Z  opted_in_users = [ 2025-07-17T06:33:20.5635824Z  requestor 2025-07-17T06:33:20.5636353Z  for requestor in workflow_requestors 2025-07-17T06:33:20.5637078Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:33:20.5637747Z  ] 2025-07-17T06:33:20.5638142Z  2025-07-17T06:33:20.5638534Z  enabled = False 2025-07-17T06:33:20.5639040Z  if opted_in_users: 2025-07-17T06:33:20.5639667Z  log.info( 2025-07-17T06:33:20.5640358Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:33:20.5641089Z  ) 2025-07-17T06:33:20.5641524Z  enabled = True 2025-07-17T06:33:20.5642011Z  2025-07-17T06:33:20.5642455Z  elif experiment_settings.rollout_perc: 2025-07-17T06:33:20.5643447Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:33:20.5644480Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:33:20.5645188Z  log.info( 2025-07-17T06:33:20.5646142Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:33:20.5647111Z  ) 2025-07-17T06:33:20.5647594Z  enabled = True 2025-07-17T06:33:20.5648101Z  2025-07-17T06:33:20.5648481Z  if enabled: 2025-07-17T06:33:20.5648980Z  label = experiment_name 2025-07-17T06:33:20.5649591Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:33:20.5650477Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:33:20.5651422Z  # - If it's enabled, then we always list it's prefix first 2025-07-17T06:33:20.5652249Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:33:20.5652971Z  if is_canary: 2025-07-17T06:33:20.5653631Z  label += CANARY_FLEET_SUFFIX 2025-07-17T06:33:20.5654232Z  fleet_prefix = label 2025-07-17T06:33:20.5654772Z  else: 2025-07-17T06:33:20.5655409Z  prefixes.append(label) 2025-07-17T06:33:20.5655955Z  2025-07-17T06:33:20.5656343Z  if len(prefixes) > 1: 2025-07-17T06:33:20.5656847Z  log.error( 2025-07-17T06:33:20.5657966Z  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-07-17T06:33:20.5659153Z  ) 2025-07-17T06:33:20.5659601Z  prefixes = prefixes[:1] 2025-07-17T06:33:20.5660111Z  2025-07-17T06:33:20.5660515Z  # Fleet always comes first 2025-07-17T06:33:20.5661036Z  if fleet_prefix: 2025-07-17T06:33:20.5661560Z  prefixes.insert(0, fleet_prefix) 2025-07-17T06:33:20.5662100Z  2025-07-17T06:33:20.5662590Z  return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:33:20.5663196Z  2025-07-17T06:33:20.5663666Z  2025-07-17T06:33:20.5664358Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:33:20.5665187Z  """ 2025-07-17T06:33:20.5665841Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:33:20.5666587Z  2025-07-17T06:33:20.5667209Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:33:20.5667961Z  """ 2025-07-17T06:33:20.5668408Z  gh = get_gh_client(github_token) 2025-07-17T06:33:20.5669098Z  issue = get_issue(gh, repo, issue_num) 2025-07-17T06:33:20.5669807Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:33:20.5670454Z  2025-07-17T06:33:20.5670819Z  2025-07-17T06:33:20.5671456Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:33:20.5672420Z  for _ in range(num_retries): 2025-07-17T06:33:20.5672958Z  try: 2025-07-17T06:33:20.5673556Z  req = Request(url=url, headers=headers) 2025-07-17T06:33:20.5674280Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:33:20.5674991Z  return json.loads(content) 2025-07-17T06:33:20.5675560Z  except Exception as e: 2025-07-17T06:33:20.5676178Z  log.warning(f"Could not download {url}: {e}") 2025-07-17T06:33:20.5676771Z  2025-07-17T06:33:20.5677396Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:33:20.5678225Z  return {} 2025-07-17T06:33:20.5678654Z  2025-07-17T06:33:20.5679017Z  2025-07-17T06:33:20.5679381Z @cache 2025-07-17T06:33:20.5680077Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:33:20.5680893Z  """ 2025-07-17T06:33:20.5681337Z  Dynamically get PR information 2025-07-17T06:33:20.5681871Z  """ 2025-07-17T06:33:20.5682434Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:33:20.5683114Z  headers = { 2025-07-17T06:33:20.5683744Z  "Accept": "application/vnd.github.v3+json", 2025-07-17T06:33:20.5684412Z  "Authorization": f"token {github_token}", 2025-07-17T06:33:20.5684994Z  } 2025-07-17T06:33:20.5685493Z  json_response: dict[str, Any] = download_json( 2025-07-17T06:33:20.5686159Z  url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:33:20.5686747Z  headers=headers, 2025-07-17T06:33:20.5687229Z  ) 2025-07-17T06:33:20.5687613Z  2025-07-17T06:33:20.5688005Z  if not json_response: 2025-07-17T06:33:20.5688651Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:33:20.5689505Z  return {} 2025-07-17T06:33:20.5689991Z  2025-07-17T06:33:20.5690390Z  return json_response 2025-07-17T06:33:20.5690878Z  2025-07-17T06:33:20.5691241Z  2025-07-17T06:33:20.5691879Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:33:20.5692657Z  """ 2025-07-17T06:33:20.5693256Z  Dynamically get the latest list of labels from the pull request 2025-07-17T06:33:20.5694070Z  """ 2025-07-17T06:33:20.5694617Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:33:20.5695287Z  return { 2025-07-17T06:33:20.5695965Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:33:20.5696709Z  } 2025-07-17T06:33:20.5697100Z  2025-07-17T06:33:20.5697535Z  2025-07-17T06:33:20.5697919Z def main() -> None: 2025-07-17T06:33:20.5698412Z  args = parse_args() 2025-07-17T06:33:20.5698889Z  2025-07-17T06:33:20.5699352Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:33:20.5699925Z  2025-07-17T06:33:20.5700338Z  # Check if the PR is opt-out 2025-07-17T06:33:20.5700886Z  if args.pr_number: 2025-07-17T06:33:20.5701634Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:33:20.5702444Z  if OPT_OUT_LABEL in labels: 2025-07-17T06:33:20.5702990Z  log.info( 2025-07-17T06:33:20.5703870Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:33:20.5704679Z  ) 2025-07-17T06:33:20.5705315Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:20.5706055Z  sys.exit() 2025-07-17T06:33:20.5706642Z  2025-07-17T06:33:20.5707030Z  try: 2025-07-17T06:33:20.5707532Z  rollout_state = get_rollout_state_from_issue( 2025-07-17T06:33:20.5708314Z  args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:33:20.5708999Z  ) 2025-07-17T06:33:20.5709404Z  2025-07-17T06:33:20.5709838Z  username = get_potential_pr_author( 2025-07-17T06:33:20.5710435Z  args.github_token, 2025-07-17T06:33:20.5710974Z  args.github_repo, 2025-07-17T06:33:20.5711510Z  args.github_actor, 2025-07-17T06:33:20.5712064Z  args.github_ref_type, 2025-07-17T06:33:20.5712612Z  args.github_branch, 2025-07-17T06:33:20.5713131Z  ) 2025-07-17T06:33:20.5713631Z  2025-07-17T06:33:20.5714161Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:33:20.5714828Z  2025-07-17T06:33:20.5715279Z  runner_label_prefix = get_runner_prefix( 2025-07-17T06:33:20.5715881Z  rollout_state, 2025-07-17T06:33:20.5716435Z  (args.github_issue_owner, username), 2025-07-17T06:33:20.5717034Z  args.github_branch, 2025-07-17T06:33:20.5717601Z  args.eligible_experiments, 2025-07-17T06:33:20.5718201Z  args.opt_out_experiments, 2025-07-17T06:33:20.5718759Z  is_canary, 2025-07-17T06:33:20.5719241Z  ) 2025-07-17T06:33:20.5719647Z  2025-07-17T06:33:20.5720042Z  except Exception as e: 2025-07-17T06:33:20.5720564Z  log.error( 2025-07-17T06:33:20.5721327Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:33:20.5722267Z  ) 2025-07-17T06:33:20.5722676Z  2025-07-17T06:33:20.5723261Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:20.5724094Z  2025-07-17T06:33:20.5724464Z  2025-07-17T06:33:20.5724863Z if __name__ == "__main__": 2025-07-17T06:33:20.5725371Z  main() 2025-07-17T06:33:20.5725787Z  2025-07-17T06:33:20.5726150Z EOF 2025-07-17T06:33:20.5726528Z  2025-07-17T06:33:20.5726925Z cat runner_determinator.py 2025-07-17T06:33:20.5934258Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:20.5935222Z env: 2025-07-17T06:33:20.5935943Z GITHUB_TOKEN: *** 2025-07-17T06:33:20.5936372Z ISSUE_NUMBER: 5132 2025-07-17T06:33:20.5936826Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:20.5937347Z ISSUE_OWNER: 2025-07-17T06:33:20.5937753Z CHECK_EXPERIMENTS: 2025-07-17T06:33:20.5938188Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:20.5938620Z PR_NUMBER: 2025-07-17T06:33:20.5939026Z ##[endgroup] 2025-07-17T06:33:20.6141733Z # flake8: noqa: G004 2025-07-17T06:33:20.6142108Z 2025-07-17T06:33:20.6142550Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:33:20.6144064Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:33:20.6144938Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:33:20.6145417Z 2025-07-17T06:33:20.6145586Z """ 2025-07-17T06:33:20.6146180Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:33:20.6147090Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:33:20.6148025Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:33:20.6148874Z of which runners should be used to run which job. 2025-07-17T06:33:20.6149307Z 2025-07-17T06:33:20.6149699Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:33:20.6150823Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:33:20.6151759Z settings are considered to be empty with only the second part, the user 2025-07-17T06:33:20.6152504Z list, defined. 2025-07-17T06:33:20.6152769Z 2025-07-17T06:33:20.6153147Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:33:20.6154686Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:33:20.6155568Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:33:20.6156046Z 2025-07-17T06:33:20.6156417Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:33:20.6157529Z The user list is also a comma separated list of additional features or 2025-07-17T06:33:20.6158447Z experiments which the user could be opted in to. 2025-07-17T06:33:20.6158889Z 2025-07-17T06:33:20.6159203Z The user list has the following rules: 2025-07-17T06:33:20.6159593Z 2025-07-17T06:33:20.6160033Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:33:20.6161066Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:33:20.6161980Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:33:20.6162548Z 2025-07-17T06:33:20.6162732Z Example config: 2025-07-17T06:33:20.6163444Z # A list of experiments that can be opted into. 2025-07-17T06:33:20.6164289Z # This defines the behavior they'll induce when opted into. 2025-07-17T06:33:20.6165121Z # Expected syntax is: 2025-07-17T06:33:20.6165894Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:33:20.6167133Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:33:20.6167869Z 2025-07-17T06:33:20.6168055Z experiments: 2025-07-17T06:33:20.6168577Z lf: 2025-07-17T06:33:20.6168998Z rollout_percent: 25 2025-07-17T06:33:20.6169699Z all_branches: false 2025-07-17T06:33:20.6170184Z default: true 2025-07-17T06:33:20.6170633Z --- 2025-07-17T06:33:20.6170860Z 2025-07-17T06:33:20.6171035Z # Opt-ins: 2025-07-17T06:33:20.6171651Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:33:20.6172569Z # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:33:20.6173543Z # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:33:20.6174251Z # Experiments should be from the above list. 2025-07-17T06:33:20.6174657Z 2025-07-17T06:33:20.6174845Z @User1,-lf,split_build 2025-07-17T06:33:20.6175320Z @User2,lf 2025-07-17T06:33:20.6175746Z @User3,split_build 2025-07-17T06:33:20.6176184Z """ 2025-07-17T06:33:20.6176394Z 2025-07-17T06:33:20.6176573Z import json 2025-07-17T06:33:20.6176978Z import logging 2025-07-17T06:33:20.6177392Z import os 2025-07-17T06:33:20.6177784Z import random 2025-07-17T06:33:20.6178192Z import re 2025-07-17T06:33:20.6178588Z import sys 2025-07-17T06:33:20.6179038Z from argparse import ArgumentParser 2025-07-17T06:33:20.6179612Z from collections.abc import Iterable 2025-07-17T06:33:20.6180175Z from functools import cache 2025-07-17T06:33:20.6180674Z from logging import LogRecord 2025-07-17T06:33:20.6181199Z from typing import Any, NamedTuple 2025-07-17T06:33:20.6181766Z from urllib.request import Request, urlopen 2025-07-17T06:33:20.6182163Z 2025-07-17T06:33:20.6182336Z import yaml 2025-07-17T06:33:20.6182755Z from github import Auth, Github 2025-07-17T06:33:20.6183266Z from github.Issue import Issue 2025-07-17T06:33:20.6184015Z 2025-07-17T06:33:20.6184025Z 2025-07-17T06:33:20.6184260Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:33:20.6184978Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:33:20.6185897Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:33:20.6186480Z 2025-07-17T06:33:20.6186725Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:33:20.6187484Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:33:20.6188049Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:33:20.6188636Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:33:20.6189023Z 2025-07-17T06:33:20.6189226Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:33:20.6189584Z 2025-07-17T06:33:20.6189777Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:33:20.6190271Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:33:20.6190580Z 2025-07-17T06:33:20.6190587Z 2025-07-17T06:33:20.6190789Z class Experiment(NamedTuple): 2025-07-17T06:33:20.6191302Z rollout_perc: float = ( 2025-07-17T06:33:20.6191974Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:33:20.6192681Z ) 2025-07-17T06:33:20.6193080Z all_branches: bool = ( 2025-07-17T06:33:20.6193974Z False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:33:20.6194690Z ) 2025-07-17T06:33:20.6195085Z default: bool = ( 2025-07-17T06:33:20.6195693Z True # If True, the experiment is enabled by default for all queries 2025-07-17T06:33:20.6196376Z ) 2025-07-17T06:33:20.6196591Z 2025-07-17T06:33:20.6196775Z # Add more fields as needed 2025-07-17T06:33:20.6197104Z 2025-07-17T06:33:20.6197118Z 2025-07-17T06:33:20.6197308Z class Settings(NamedTuple): 2025-07-17T06:33:20.6197768Z """ 2025-07-17T06:33:20.6198245Z Settings for the experiments that can be opted into. 2025-07-17T06:33:20.6198851Z """ 2025-07-17T06:33:20.6199075Z 2025-07-17T06:33:20.6199285Z experiments: dict[str, Experiment] = {} 2025-07-17T06:33:20.6199677Z 2025-07-17T06:33:20.6199684Z 2025-07-17T06:33:20.6199896Z class ColorFormatter(logging.Formatter): 2025-07-17T06:33:20.6200546Z """Color codes the log messages based on the log level""" 2025-07-17T06:33:20.6201009Z 2025-07-17T06:33:20.6201179Z COLORS = { 2025-07-17T06:33:20.6201607Z "WARNING": "\033[33m", # Yellow 2025-07-17T06:33:20.6202309Z "ERROR": "\033[31m", # Red 2025-07-17T06:33:20.6202848Z "CRITICAL": "\033[31m", # Red 2025-07-17T06:33:20.6203582Z "INFO": "\033[0m", # Reset 2025-07-17T06:33:20.6204123Z "DEBUG": "\033[0m", # Reset 2025-07-17T06:33:20.6204624Z } 2025-07-17T06:33:20.6204843Z 2025-07-17T06:33:20.6205075Z def format(self, record: LogRecord) -> str: 2025-07-17T06:33:20.6205861Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:33:20.6206675Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:33:20.6207285Z return super().format(record) 2025-07-17T06:33:20.6207652Z 2025-07-17T06:33:20.6207659Z 2025-07-17T06:33:20.6207855Z handler = logging.StreamHandler() 2025-07-17T06:33:20.6208586Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:33:20.6209162Z 2025-07-17T06:33:20.6209403Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:33:20.6210025Z log.addHandler(handler) 2025-07-17T06:33:20.6210497Z log.setLevel(logging.INFO) 2025-07-17T06:33:20.6210812Z 2025-07-17T06:33:20.6210819Z 2025-07-17T06:33:20.6211072Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:33:20.6211660Z """ 2025-07-17T06:33:20.6212190Z Defines outputs of the github action that invokes this script 2025-07-17T06:33:20.6212854Z """ 2025-07-17T06:33:20.6213246Z if not GITHUB_OUTPUT: 2025-07-17T06:33:20.6214574Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:33:20.6215752Z log.warning( 2025-07-17T06:33:20.6216641Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-07-17T06:33:20.6217612Z ) 2025-07-17T06:33:20.6227456Z print(f"::set-output name={key}::{value}") 2025-07-17T06:33:20.6228103Z return 2025-07-17T06:33:20.6228376Z 2025-07-17T06:33:20.6228787Z with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:33:20.6229438Z log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:33:20.6230041Z f.write(f"{key}={value}\n") 2025-07-17T06:33:20.6230391Z 2025-07-17T06:33:20.6230406Z 2025-07-17T06:33:20.6230708Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:33:20.6231392Z return frozenset( 2025-07-17T06:33:20.6232036Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:33:20.6232754Z ) 2025-07-17T06:33:20.6232981Z 2025-07-17T06:33:20.6232988Z 2025-07-17T06:33:20.6233178Z def parse_args() -> Any: 2025-07-17T06:33:20.6234025Z parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:33:20.6234923Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:33:20.6235726Z parser.add_argument( 2025-07-17T06:33:20.6236213Z "--github-issue-repo", 2025-07-17T06:33:20.6236721Z type=str, 2025-07-17T06:33:20.6237149Z required=False, 2025-07-17T06:33:20.6237639Z default="pytorch/test-infra", 2025-07-17T06:33:20.6238210Z help="GitHub repo to get the issue", 2025-07-17T06:33:20.6238752Z ) 2025-07-17T06:33:20.6239144Z parser.add_argument( 2025-07-17T06:33:20.6239617Z "--github-repo", 2025-07-17T06:33:20.6240088Z type=str, 2025-07-17T06:33:20.6240512Z required=True, 2025-07-17T06:33:20.6241001Z help="GitHub repo where CI is running", 2025-07-17T06:33:20.6241548Z ) 2025-07-17T06:33:20.6241950Z parser.add_argument( 2025-07-17T06:33:20.6242579Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:33:20.6243269Z ) 2025-07-17T06:33:20.6243888Z parser.add_argument( 2025-07-17T06:33:20.6244543Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:33:20.6294210Z ) 2025-07-17T06:33:20.6294726Z parser.add_argument( 2025-07-17T06:33:20.6295701Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:33:20.6296434Z ) 2025-07-17T06:33:20.6296841Z parser.add_argument( 2025-07-17T06:33:20.6297518Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:33:20.6298292Z ) 2025-07-17T06:33:20.6298692Z parser.add_argument( 2025-07-17T06:33:20.6299189Z "--github-ref-type", 2025-07-17T06:33:20.6299679Z type=str, 2025-07-17T06:33:20.6300106Z required=True, 2025-07-17T06:33:20.6300620Z help="Current GitHub ref type, branch or tag", 2025-07-17T06:33:20.6301207Z ) 2025-07-17T06:33:20.6301617Z parser.add_argument( 2025-07-17T06:33:20.6302099Z "--eligible-experiments", 2025-07-17T06:33:20.6302646Z type=_str_comma_separated_to_set, 2025-07-17T06:33:20.6303200Z required=False, 2025-07-17T06:33:20.6303928Z default="", 2025-07-17T06:33:20.6304829Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:33:20.6305801Z ) 2025-07-17T06:33:20.6306199Z parser.add_argument( 2025-07-17T06:33:20.6306677Z "--opt-out-experiments", 2025-07-17T06:33:20.6307210Z type=_str_comma_separated_to_set, 2025-07-17T06:33:20.6307750Z required=False, 2025-07-17T06:33:20.6308204Z default="", 2025-07-17T06:33:20.6308623Z help=( 2025-07-17T06:33:20.6309317Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:33:20.6310484Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:33:20.6311371Z ), 2025-07-17T06:33:20.6311759Z ) 2025-07-17T06:33:20.6312155Z parser.add_argument( 2025-07-17T06:33:20.6312632Z "--pr-number", 2025-07-17T06:33:20.6313066Z type=str, 2025-07-17T06:33:20.6313644Z required=False, 2025-07-17T06:33:20.6314095Z default="", 2025-07-17T06:33:20.6314742Z help="the optional PR number where this is run", 2025-07-17T06:33:20.6315343Z ) 2025-07-17T06:33:20.6315573Z 2025-07-17T06:33:20.6315768Z return parser.parse_args() 2025-07-17T06:33:20.6316093Z 2025-07-17T06:33:20.6316100Z 2025-07-17T06:33:20.6316512Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:33:20.6317307Z auth = Auth.Token(github_token) 2025-07-17T06:33:20.6317841Z return Github(auth=auth) 2025-07-17T06:33:20.6318158Z 2025-07-17T06:33:20.6318165Z 2025-07-17T06:33:20.6318616Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:33:20.6319451Z repo = gh.get_repo(repo) 2025-07-17T06:33:20.6319982Z return repo.get_issue(number=issue_num) 2025-07-17T06:33:20.6320369Z 2025-07-17T06:33:20.6320376Z 2025-07-17T06:33:20.6320570Z def get_potential_pr_author( 2025-07-17T06:33:20.6321245Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:33:20.6321956Z ) -> str: 2025-07-17T06:33:20.6322500Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:33:20.6323426Z # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:33:20.6324201Z # embedded in the tag name: ciflow// 2025-07-17T06:33:20.6324638Z 2025-07-17T06:33:20.6324839Z gh = get_gh_client(github_token) 2025-07-17T06:33:20.6325196Z 2025-07-17T06:33:20.6325461Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:33:20.6326128Z split_tag = ref_name.split("/") 2025-07-17T06:33:20.6326659Z if ( 2025-07-17T06:33:20.6327078Z len(split_tag) == 3 2025-07-17T06:33:20.6327579Z and split_tag[0] == "ciflow" 2025-07-17T06:33:20.6328136Z and split_tag[2].isnumeric() 2025-07-17T06:33:20.6328651Z ): 2025-07-17T06:33:20.6329069Z pr_number = split_tag[2] 2025-07-17T06:33:20.6329711Z try: 2025-07-17T06:33:20.6330169Z repository = gh.get_repo(repo) 2025-07-17T06:33:20.6330809Z pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:33:20.6331430Z except Exception as e: 2025-07-17T06:33:20.6331979Z raise Exception( # noqa: TRY002 2025-07-17T06:33:20.6332671Z f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:33:20.6333459Z ) from e 2025-07-17T06:33:20.6334023Z return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:33:20.6334741Z # In all other cases, return the original input username 2025-07-17T06:33:20.6335360Z return username 2025-07-17T06:33:20.6335626Z 2025-07-17T06:33:20.6335632Z 2025-07-17T06:33:20.6335855Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:33:20.6336424Z """ 2025-07-17T06:33:20.6337079Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:33:20.6337900Z """ 2025-07-17T06:33:20.6338463Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:33:20.6339014Z 2025-07-17T06:33:20.6339020Z 2025-07-17T06:33:20.6339219Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:33:20.6339738Z try: 2025-07-17T06:33:20.6340144Z data = yaml.safe_load(yaml_text) 2025-07-17T06:33:20.6340674Z return data 2025-07-17T06:33:20.6341113Z except yaml.YAMLError: 2025-07-17T06:33:20.6341627Z log.exception("Error loading YAML") 2025-07-17T06:33:20.6342160Z raise 2025-07-17T06:33:20.6342408Z 2025-07-17T06:33:20.6342416Z 2025-07-17T06:33:20.6342839Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:33:20.6343728Z """ 2025-07-17T06:33:20.6344382Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:33:20.6345012Z 2025-07-17T06:33:20.6345497Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:20.6346286Z and the text below is the list of opted in users. 2025-07-17T06:33:20.6346712Z 2025-07-17T06:33:20.6347088Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:33:20.6347818Z """ 2025-07-17T06:33:20.6348283Z rollout_state_parts = rollout_state.split("---") 2025-07-17T06:33:20.6348936Z if len(rollout_state_parts) >= 2: 2025-07-17T06:33:20.6349564Z return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:33:20.6350179Z else: 2025-07-17T06:33:20.6350574Z return "", rollout_state 2025-07-17T06:33:20.6350904Z 2025-07-17T06:33:20.6350916Z 2025-07-17T06:33:20.6351121Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:33:20.6351650Z """ 2025-07-17T06:33:20.6352199Z Dictionary of users with a list of features they have opted into 2025-07-17T06:33:20.6352875Z """ 2025-07-17T06:33:20.6353101Z 2025-07-17T06:33:20.6353109Z 2025-07-17T06:33:20.6353555Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:33:20.6354254Z """ 2025-07-17T06:33:20.6355572Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-07-17T06:33:20.6356546Z 2025-07-17T06:33:20.6357451Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:33:20.6358636Z - Example line: "@User1,lf,split_build" 2025-07-17T06:33:20.6359353Z - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:33:20.6359858Z 2025-07-17T06:33:20.6359866Z 2025-07-17T06:33:20.6360040Z """ 2025-07-17T06:33:20.6360443Z optins = UserOptins() 2025-07-17T06:33:20.6360955Z for user in user_optin_text.split("\n"): 2025-07-17T06:33:20.6361538Z user = user.strip("\r\n\t -") 2025-07-17T06:33:20.6362145Z if not user or not user.startswith("@"): 2025-07-17T06:33:20.6362896Z # Not a valid user. Skip 2025-07-17T06:33:20.6363528Z continue 2025-07-17T06:33:20.6363802Z 2025-07-17T06:33:20.6363969Z if user: 2025-07-17T06:33:20.6364435Z usr_name = user.split(",")[0].strip("@") 2025-07-17T06:33:20.6365149Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:33:20.6365669Z 2025-07-17T06:33:20.6365836Z return optins 2025-07-17T06:33:20.6366098Z 2025-07-17T06:33:20.6366106Z 2025-07-17T06:33:20.6366405Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:33:20.6367034Z """ 2025-07-17T06:33:20.6367456Z Check if the experiment name is valid. 2025-07-17T06:33:20.6368000Z A valid name: 2025-07-17T06:33:20.6368663Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:33:20.6369648Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:33:20.6370406Z - Cannot contain spaces 2025-07-17T06:33:20.6370895Z """ 2025-07-17T06:33:20.6371114Z 2025-07-17T06:33:20.6371375Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:33:20.6372101Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:33:20.6372572Z 2025-07-17T06:33:20.6372734Z if valid: 2025-07-17T06:33:20.6373137Z return True 2025-07-17T06:33:20.6373503Z 2025-07-17T06:33:20.6373677Z log.error( 2025-07-17T06:33:20.6375164Z 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-07-17T06:33:20.6376760Z ) 2025-07-17T06:33:20.6377144Z return False 2025-07-17T06:33:20.6377409Z 2025-07-17T06:33:20.6377415Z 2025-07-17T06:33:20.6377722Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:33:20.6378379Z """ 2025-07-17T06:33:20.6379111Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:33:20.6379876Z """ 2025-07-17T06:33:20.6380245Z try: 2025-07-17T06:33:20.6380646Z if settings_text: 2025-07-17T06:33:20.6381400Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:33:20.6382233Z # for easy reading 2025-07-17T06:33:20.6383050Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:33:20.6384096Z # the backtick character in shell commands. 2025-07-17T06:33:20.6384732Z backtick = chr(96) # backtick character 2025-07-17T06:33:20.6385425Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:33:20.6386133Z settings = load_yaml(settings_text) 2025-07-17T06:33:20.6386535Z 2025-07-17T06:33:20.6386955Z # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:33:20.6387769Z experiments = {} 2025-07-17T06:33:20.6388085Z 2025-07-17T06:33:20.6388474Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:33:20.6389273Z if not is_valid_experiment_name(exp_name): 2025-07-17T06:33:20.6390426Z # 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-07-17T06:33:20.6391510Z continue 2025-07-17T06:33:20.6391826Z 2025-07-17T06:33:20.6392013Z valid_settings = {} 2025-07-17T06:33:20.6392565Z for setting in exp_settings: 2025-07-17T06:33:20.6393161Z if setting not in Experiment._fields: 2025-07-17T06:33:20.6393860Z log.warning( 2025-07-17T06:33:20.6394588Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:33:20.6395507Z ) 2025-07-17T06:33:20.6395965Z else: 2025-07-17T06:33:20.6396509Z valid_settings[setting] = exp_settings[setting] 2025-07-17T06:33:20.6396952Z 2025-07-17T06:33:20.6397231Z experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:33:20.6397896Z return Settings(experiments) 2025-07-17T06:33:20.6398271Z 2025-07-17T06:33:20.6398450Z except Exception: 2025-07-17T06:33:20.6398941Z log.exception("Failed to parse settings") 2025-07-17T06:33:20.6399357Z 2025-07-17T06:33:20.6399543Z return Settings() 2025-07-17T06:33:20.6399823Z 2025-07-17T06:33:20.6399830Z 2025-07-17T06:33:20.6400089Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:33:20.6400893Z """ 2025-07-17T06:33:20.6401374Z Parse settings, if any, from the rollout state. 2025-07-17T06:33:20.6401816Z 2025-07-17T06:33:20.6402172Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:20.6402975Z and the text below is the list of opted in users. 2025-07-17T06:33:20.6403529Z 2025-07-17T06:33:20.6403951Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:33:20.6404727Z """ 2025-07-17T06:33:20.6405294Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:20.6406079Z return parse_settings_from_text(settings_text) 2025-07-17T06:33:20.6406497Z 2025-07-17T06:33:20.6406504Z 2025-07-17T06:33:20.6406759Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:33:20.6407339Z """ 2025-07-17T06:33:20.6407756Z Parse users from the rollout state. 2025-07-17T06:33:20.6408132Z 2025-07-17T06:33:20.6408290Z """ 2025-07-17T06:33:20.6408849Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:20.6409623Z return parse_user_opt_in_from_text(users_text) 2025-07-17T06:33:20.6410062Z 2025-07-17T06:33:20.6410068Z 2025-07-17T06:33:20.6410657Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:20.6411450Z """ 2025-07-17T06:33:20.6411883Z Check if a user is opted into an experiment 2025-07-17T06:33:20.6412454Z """ 2025-07-17T06:33:20.6412923Z return experiment_name in user_optins.get(user, []) 2025-07-17T06:33:20.6413484Z 2025-07-17T06:33:20.6413491Z 2025-07-17T06:33:20.6413926Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:20.6414710Z """ 2025-07-17T06:33:20.6415197Z Check if a user explicitly opted out of an experiment 2025-07-17T06:33:20.6415806Z """ 2025-07-17T06:33:20.6416329Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:33:20.6417051Z experiment_optout = "-" + experiment_name 2025-07-17T06:33:20.6417718Z if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:33:20.6418357Z return False 2025-07-17T06:33:20.6418640Z 2025-07-17T06:33:20.6418918Z if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:33:20.6419556Z log.warning( 2025-07-17T06:33:20.6420385Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:33:20.6421307Z ) 2025-07-17T06:33:20.6421529Z 2025-07-17T06:33:20.6421705Z return True 2025-07-17T06:33:20.6421961Z 2025-07-17T06:33:20.6421969Z 2025-07-17T06:33:20.6422148Z def get_runner_prefix( 2025-07-17T06:33:20.6422611Z rollout_state: str, 2025-07-17T06:33:20.6423095Z workflow_requestors: Iterable[str], 2025-07-17T06:33:20.6423747Z branch: str, 2025-07-17T06:33:20.6424269Z eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:20.6424957Z opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:20.6425575Z is_canary: bool = False, 2025-07-17T06:33:20.6426065Z ) -> str: 2025-07-17T06:33:20.6426662Z settings = parse_settings(rollout_state) 2025-07-17T06:33:20.6427274Z user_optins = parse_users(rollout_state) 2025-07-17T06:33:20.6427675Z 2025-07-17T06:33:20.6427851Z fleet_prefix = "" 2025-07-17T06:33:20.6428287Z prefixes = [] 2025-07-17T06:33:20.6428940Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:33:20.6429914Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:33:20.6430655Z log.info( 2025-07-17T06:33:20.6431366Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:33:20.6432144Z ) 2025-07-17T06:33:20.6432548Z continue 2025-07-17T06:33:20.6432812Z 2025-07-17T06:33:20.6433004Z if opt_out_experiments: 2025-07-17T06:33:20.6433677Z if experiment_name in opt_out_experiments: 2025-07-17T06:33:20.6434346Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:33:20.6434981Z log.info( 2025-07-17T06:33:20.6435945Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:33:20.6436950Z ) 2025-07-17T06:33:20.6437371Z continue 2025-07-17T06:33:20.6437659Z 2025-07-17T06:33:20.6437850Z if eligible_experiments: 2025-07-17T06:33:20.6438463Z if experiment_name not in eligible_experiments: 2025-07-17T06:33:20.6439127Z exp_list = ", ".join(eligible_experiments) 2025-07-17T06:33:20.6439717Z log.info( 2025-07-17T06:33:20.6440525Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:33:20.6441393Z ) 2025-07-17T06:33:20.6441811Z continue 2025-07-17T06:33:20.6442307Z elif not experiment_settings.default: 2025-07-17T06:33:20.6442859Z log.info( 2025-07-17T06:33:20.6443751Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:33:20.6444536Z ) 2025-07-17T06:33:20.6444934Z continue 2025-07-17T06:33:20.6445205Z 2025-07-17T06:33:20.6445481Z # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:33:20.6446128Z opted_out_users = [ 2025-07-17T06:33:20.6446598Z requestor 2025-07-17T06:33:20.6447072Z for requestor in workflow_requestors 2025-07-17T06:33:20.6447759Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:33:20.6448407Z ] 2025-07-17T06:33:20.6448628Z 2025-07-17T06:33:20.6448802Z if opted_out_users: 2025-07-17T06:33:20.6449269Z log.info( 2025-07-17T06:33:20.6449902Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:33:20.6450626Z ) 2025-07-17T06:33:20.6451024Z continue 2025-07-17T06:33:20.6451293Z 2025-07-17T06:33:20.6451580Z # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:33:20.6452242Z opted_in_users = [ 2025-07-17T06:33:20.6452707Z requestor 2025-07-17T06:33:20.6453189Z for requestor in workflow_requestors 2025-07-17T06:33:20.6453999Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:33:20.6454655Z ] 2025-07-17T06:33:20.6454876Z 2025-07-17T06:33:20.6455056Z enabled = False 2025-07-17T06:33:20.6455513Z if opted_in_users: 2025-07-17T06:33:20.6456010Z log.info( 2025-07-17T06:33:20.6456636Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:33:20.6457355Z ) 2025-07-17T06:33:20.6457766Z enabled = True 2025-07-17T06:33:20.6458073Z 2025-07-17T06:33:20.6458290Z elif experiment_settings.rollout_perc: 2025-07-17T06:33:20.6459160Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:33:20.6460307Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:33:20.6460989Z log.info( 2025-07-17T06:33:20.6461880Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:33:20.6462875Z ) 2025-07-17T06:33:20.6463298Z enabled = True 2025-07-17T06:33:20.6464111Z 2025-07-17T06:33:20.6464284Z if enabled: 2025-07-17T06:33:20.6464728Z label = experiment_name 2025-07-17T06:33:20.6465308Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:33:20.6466191Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:33:20.6467109Z # - If it's enabled, then we always list it's prefix first 2025-07-17T06:33:20.6467902Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:33:20.6468608Z if is_canary: 2025-07-17T06:33:20.6469123Z label += CANARY_FLEET_SUFFIX 2025-07-17T06:33:20.6469701Z fleet_prefix = label 2025-07-17T06:33:20.6470220Z else: 2025-07-17T06:33:20.6470659Z prefixes.append(label) 2025-07-17T06:33:20.6471032Z 2025-07-17T06:33:20.6471212Z if len(prefixes) > 1: 2025-07-17T06:33:20.6471683Z log.error( 2025-07-17T06:33:20.6472744Z 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-07-17T06:33:20.6474019Z ) 2025-07-17T06:33:20.6474435Z prefixes = prefixes[:1] 2025-07-17T06:33:20.6474773Z 2025-07-17T06:33:20.6474962Z # Fleet always comes first 2025-07-17T06:33:20.6475457Z if fleet_prefix: 2025-07-17T06:33:20.6475931Z prefixes.insert(0, fleet_prefix) 2025-07-17T06:33:20.6476320Z 2025-07-17T06:33:20.6476709Z return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:33:20.6477157Z 2025-07-17T06:33:20.6477164Z 2025-07-17T06:33:20.6477610Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:33:20.6478406Z """ 2025-07-17T06:33:20.6479005Z Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:33:20.6479606Z 2025-07-17T06:33:20.6479988Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:33:20.6480715Z """ 2025-07-17T06:33:20.6481118Z gh = get_gh_client(github_token) 2025-07-17T06:33:20.6481682Z issue = get_issue(gh, repo, issue_num) 2025-07-17T06:33:20.6482326Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:33:20.6482796Z 2025-07-17T06:33:20.6482809Z 2025-07-17T06:33:20.6483215Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:33:20.6484114Z for _ in range(num_retries): 2025-07-17T06:33:20.6484617Z try: 2025-07-17T06:33:20.6485059Z req = Request(url=url, headers=headers) 2025-07-17T06:33:20.6485750Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:33:20.6486418Z return json.loads(content) 2025-07-17T06:33:20.6486966Z except Exception as e: 2025-07-17T06:33:20.6487534Z log.warning(f"Could not download {url}: {e}") 2025-07-17T06:33:20.6487961Z 2025-07-17T06:33:20.6488354Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:33:20.6489091Z return {} 2025-07-17T06:33:20.6489327Z 2025-07-17T06:33:20.6489334Z 2025-07-17T06:33:20.6489503Z @cache 2025-07-17T06:33:20.6490140Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:33:20.6490935Z """ 2025-07-17T06:33:20.6491346Z Dynamically get PR information 2025-07-17T06:33:20.6491851Z """ 2025-07-17T06:33:20.6492509Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:33:20.6493168Z headers = { 2025-07-17T06:33:20.6493745Z "Accept": "application/vnd.github.v3+json", 2025-07-17T06:33:20.6494380Z "Authorization": f"token {github_token}", 2025-07-17T06:33:20.6494939Z } 2025-07-17T06:33:20.6495390Z json_response: dict[str, Any] = download_json( 2025-07-17T06:33:20.6496021Z url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:33:20.6496598Z headers=headers, 2025-07-17T06:33:20.6497047Z ) 2025-07-17T06:33:20.6497258Z 2025-07-17T06:33:20.6497441Z if not json_response: 2025-07-17T06:33:20.6498035Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:33:20.6498667Z return {} 2025-07-17T06:33:20.6498934Z 2025-07-17T06:33:20.6499111Z return json_response 2025-07-17T06:33:20.6499418Z 2025-07-17T06:33:20.6499426Z 2025-07-17T06:33:20.6499828Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:33:20.6500594Z """ 2025-07-17T06:33:20.6501142Z Dynamically get the latest list of labels from the pull request 2025-07-17T06:33:20.6501823Z """ 2025-07-17T06:33:20.6502326Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:33:20.6502952Z return { 2025-07-17T06:33:20.6503663Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:33:20.6504388Z } 2025-07-17T06:33:20.6504612Z 2025-07-17T06:33:20.6504620Z 2025-07-17T06:33:20.6504792Z def main() -> None: 2025-07-17T06:33:20.6505229Z args = parse_args() 2025-07-17T06:33:20.6505512Z 2025-07-17T06:33:20.6505733Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:33:20.6506134Z 2025-07-17T06:33:20.6506330Z # Check if the PR is opt-out 2025-07-17T06:33:20.6506833Z if args.pr_number: 2025-07-17T06:33:20.6507508Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:33:20.6508417Z if OPT_OUT_LABEL in labels: 2025-07-17T06:33:20.6508949Z log.info( 2025-07-17T06:33:20.6509660Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:33:20.6510442Z ) 2025-07-17T06:33:20.6511005Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:20.6511689Z sys.exit() 2025-07-17T06:33:20.6511969Z 2025-07-17T06:33:20.6512134Z try: 2025-07-17T06:33:20.6512587Z rollout_state = get_rollout_state_from_issue( 2025-07-17T06:33:20.6513414Z args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:33:20.6514086Z ) 2025-07-17T06:33:20.6514317Z 2025-07-17T06:33:20.6514524Z username = get_potential_pr_author( 2025-07-17T06:33:20.6515094Z args.github_token, 2025-07-17T06:33:20.6515590Z args.github_repo, 2025-07-17T06:33:20.6516088Z args.github_actor, 2025-07-17T06:33:20.6516597Z args.github_ref_type, 2025-07-17T06:33:20.6517114Z args.github_branch, 2025-07-17T06:33:20.6517595Z ) 2025-07-17T06:33:20.6517825Z 2025-07-17T06:33:20.6518109Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:33:20.6518587Z 2025-07-17T06:33:20.6518808Z runner_label_prefix = get_runner_prefix( 2025-07-17T06:33:20.6519393Z rollout_state, 2025-07-17T06:33:20.6519906Z (args.github_issue_owner, username), 2025-07-17T06:33:20.6520471Z args.github_branch, 2025-07-17T06:33:20.6520994Z args.eligible_experiments, 2025-07-17T06:33:20.6521554Z args.opt_out_experiments, 2025-07-17T06:33:20.6522086Z is_canary, 2025-07-17T06:33:20.6522515Z ) 2025-07-17T06:33:20.6522746Z 2025-07-17T06:33:20.6522932Z except Exception as e: 2025-07-17T06:33:20.6523491Z log.error( 2025-07-17T06:33:20.6524185Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:33:20.6525106Z ) 2025-07-17T06:33:20.6525326Z 2025-07-17T06:33:20.6525652Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:20.6526169Z 2025-07-17T06:33:20.6526181Z 2025-07-17T06:33:20.6526358Z if __name__ == "__main__": 2025-07-17T06:33:20.6526808Z main() 2025-07-17T06:33:20.6527041Z 2025-07-17T06:33:20.6617588Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:33:20.6618456Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:33:20.6646587Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:20.6647061Z env: 2025-07-17T06:33:20.6647667Z GITHUB_TOKEN: *** 2025-07-17T06:33:20.6648092Z ISSUE_NUMBER: 5132 2025-07-17T06:33:20.6648530Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:20.6649049Z ISSUE_OWNER: 2025-07-17T06:33:20.6649445Z CHECK_EXPERIMENTS: 2025-07-17T06:33:20.6649866Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:20.6650297Z PR_NUMBER: 2025-07-17T06:33:20.6650664Z ##[endgroup] 2025-07-17T06:33:21.5075416Z Defaulting to user installation because normal site-packages is not writeable 2025-07-17T06:33:22.4320806Z Collecting urllib3==1.26.18 2025-07-17T06:33:22.5154229Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-07-17T06:33:22.5420120Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.4 MB/s eta 0:00:00 2025-07-17T06:33:22.5775139Z Collecting PyGithub==2.3.0 2025-07-17T06:33:22.5944266Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-07-17T06:33:22.6513223Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-07-17T06:33:22.6672011Z 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-07-17T06:33:22.6718793Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-07-17T06:33:22.6735528Z 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-07-17T06:33:22.6750115Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-07-17T06:33:22.7133229Z Collecting Deprecated (from PyGithub==2.3.0) 2025-07-17T06:33:22.7302569Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-07-17T06:33:22.7536069Z 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-07-17T06:33:22.8974539Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:33:22.9133989Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-07-17T06:33:23.0382707Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-07-17T06:33:23.0544151Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.4 kB) 2025-07-17T06:33:23.0851911Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:33:23.1007191Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-07-17T06:33:23.1353618Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-07-17T06:33:23.1546797Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 7.7 MB/s eta 0:00:00 2025-07-17T06:33:23.1747758Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-07-17T06:33:23.1959048Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 18.1 MB/s eta 0:00:00 2025-07-17T06:33:23.2121366Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-07-17T06:33:23.2320530Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 47.6 MB/s eta 0:00:00 2025-07-17T06:33:23.2475030Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-07-17T06:33:23.2658683Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-07-17T06:33:23.2729170Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 96.2 MB/s eta 0:00:00 2025-07-17T06:33:23.2889624Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (89 kB) 2025-07-17T06:33:23.2936691Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 26.6 MB/s eta 0:00:00 2025-07-17T06:33:23.3093089Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-07-17T06:33:23.3136108Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 40.1 MB/s eta 0:00:00 2025-07-17T06:33:23.6244041Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-07-17T06:33:24.1584699Z 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.2 2025-07-17T06:33:24.2469001Z ##[group]Run curr_branch="main" 2025-07-17T06:33:24.2469325Z curr_branch="main" 2025-07-17T06:33:24.2469575Z curr_ref_type="branch" 2025-07-17T06:33:24.2469831Z echo "Current branch is '$curr_branch'" 2025-07-17T06:33:24.2470083Z  2025-07-17T06:33:24.2470275Z python3 runner_determinator.py \ 2025-07-17T06:33:24.2470548Z  --github-token "$GITHUB_TOKEN" \ 2025-07-17T06:33:24.2470824Z  --github-issue "$ISSUE_NUMBER" \ 2025-07-17T06:33:24.2471106Z  --github-branch "$curr_branch" \ 2025-07-17T06:33:24.2471367Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-07-17T06:33:24.2471647Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-07-17T06:33:24.2471916Z  --github-ref-type "$curr_ref_type" \ 2025-07-17T06:33:24.2472183Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-07-17T06:33:24.2472486Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-07-17T06:33:24.2472884Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-07-17T06:33:24.2473174Z  --pr-number "${PR_NUMBER}" 2025-07-17T06:33:24.2502399Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:24.2502622Z env: 2025-07-17T06:33:24.2503200Z GITHUB_TOKEN: *** 2025-07-17T06:33:24.2503656Z ISSUE_NUMBER: 5132 2025-07-17T06:33:24.2503928Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:24.2504159Z ISSUE_OWNER: 2025-07-17T06:33:24.2504338Z CHECK_EXPERIMENTS: 2025-07-17T06:33:24.2504527Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:24.2504718Z PR_NUMBER: 2025-07-17T06:33:24.2504884Z ##[endgroup] 2025-07-17T06:33:24.2552382Z Current branch is 'main' 2025-07-17T06:33:25.5683878Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-07-17T06:33:25.5685082Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-07-17T06:33:25.5685790Z INFO : Setting output: label-type='' 2025-07-17T06:33:25.5993172Z Evaluate and set job outputs 2025-07-17T06:33:25.6000176Z Cleaning up orphan processes