2025-09-07T07:04:16.4151341Z Current runner version: '2.328.0' 2025-09-07T07:04:16.4186003Z ##[group]Runner Image Provisioner 2025-09-07T07:04:16.4187487Z Hosted Compute Agent 2025-09-07T07:04:16.4188607Z Version: 20250829.383 2025-09-07T07:04:16.4189702Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T07:04:16.4190909Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T07:04:16.4192094Z ##[endgroup] 2025-09-07T07:04:16.4193069Z ##[group]Operating System 2025-09-07T07:04:16.4194037Z Ubuntu 2025-09-07T07:04:16.4194870Z 24.04.3 2025-09-07T07:04:16.4195705Z LTS 2025-09-07T07:04:16.4196432Z ##[endgroup] 2025-09-07T07:04:16.4197653Z ##[group]Runner Image 2025-09-07T07:04:16.4198785Z Image: ubuntu-24.04 2025-09-07T07:04:16.4199680Z Version: 20250831.1.0 2025-09-07T07:04:16.4201657Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T07:04:16.4204469Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T07:04:16.4206399Z ##[endgroup] 2025-09-07T07:04:16.4208468Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T07:04:16.4211613Z Contents: read 2025-09-07T07:04:16.4212503Z Metadata: read 2025-09-07T07:04:16.4213284Z ##[endgroup] 2025-09-07T07:04:16.4216385Z Secret source: Actions 2025-09-07T07:04:16.4217718Z Prepare workflow directory 2025-09-07T07:04:16.4972179Z Prepare all required actions 2025-09-07T07:04:16.5057853Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T07:04:16.5065076Z ##[group] Inputs 2025-09-07T07:04:16.5066046Z check_experiments: 2025-09-07T07:04:16.5067020Z opt_out_experiments: lf 2025-09-07T07:04:16.5068390Z triggering_actor: pytorchmergebot 2025-09-07T07:04:16.5069463Z issue_owner: 2025-09-07T07:04:16.5070441Z curr_branch: main 2025-09-07T07:04:16.5071545Z curr_ref_type: branch 2025-09-07T07:04:16.5072753Z issue_number: 5132 2025-09-07T07:04:16.5073800Z ##[endgroup] 2025-09-07T07:04:16.5074954Z Complete job name: get-label-type / runner-determinator 2025-09-07T07:04:16.9956372Z ##[group]Run cat < runner_determinator.py 2025-09-07T07:04:16.9959038Z cat < runner_determinator.py 2025-09-07T07:04:16.9959718Z # flake8: noqa: G004 2025-09-07T07:04:16.9960294Z  2025-09-07T07:04:16.9961121Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T07:04:16.9962245Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T07:04:16.9963251Z # python .github/scripts/update_runner_determinator.py 2025-09-07T07:04:16.9963985Z  2025-09-07T07:04:16.9964474Z """ 2025-09-07T07:04:16.9965196Z This runner determinator is used to determine which set of runners to run a 2025-09-07T07:04:16.9966283Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T07:04:16.9967682Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T07:04:16.9968702Z of which runners should be used to run which job. 2025-09-07T07:04:16.9969470Z  2025-09-07T07:04:16.9970212Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T07:04:16.9971278Z separated by a line containing "---". If the line is not present, the 2025-09-07T07:04:16.9972360Z settings are considered to be empty with only the second part, the user 2025-09-07T07:04:16.9973240Z list, defined. 2025-09-07T07:04:16.9973747Z  2025-09-07T07:04:16.9974520Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T07:04:16.9975614Z used to define any settings that are needed to determine which runners to use. 2025-09-07T07:04:16.9976596Z It's fields are defined by the RolloutSettings class below. 2025-09-07T07:04:16.9977826Z  2025-09-07T07:04:16.9979017Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T07:04:16.9980096Z The user list is also a comma separated list of additional features or 2025-09-07T07:04:16.9981096Z experiments which the user could be opted in to. 2025-09-07T07:04:16.9981768Z  2025-09-07T07:04:16.9982299Z The user list has the following rules: 2025-09-07T07:04:16.9982950Z  2025-09-07T07:04:16.9983842Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T07:04:16.9984886Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T07:04:16.9985886Z - A "#" prefix opts the user out of all experiments 2025-09-07T07:04:16.9986600Z  2025-09-07T07:04:16.9987054Z Example config: 2025-09-07T07:04:16.9987994Z  # A list of experiments that can be opted into. 2025-09-07T07:04:16.9988862Z  # This defines the behavior they'll induce when opted into. 2025-09-07T07:04:16.9989652Z  # Expected syntax is: 2025-09-07T07:04:16.9990532Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T07:04:16.9991655Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T07:04:16.9992545Z  2025-09-07T07:04:16.9992976Z  experiments: 2025-09-07T07:04:16.9993636Z  lf: 2025-09-07T07:04:16.9994132Z  rollout_percent: 25 2025-09-07T07:04:16.9994744Z  all_branches: false 2025-09-07T07:04:16.9995419Z  default: true 2025-09-07T07:04:16.9995945Z  --- 2025-09-07T07:04:16.9996425Z  2025-09-07T07:04:16.9996968Z  # Opt-ins: 2025-09-07T07:04:16.9997876Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T07:04:16.9999076Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T07:04:17.0000123Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T07:04:17.0000956Z  # Experiments should be from the above list. 2025-09-07T07:04:17.0001605Z  2025-09-07T07:04:17.0002201Z  @User1,-lf,split_build 2025-09-07T07:04:17.0002782Z  @User2,lf 2025-09-07T07:04:17.0003341Z  @User3,split_build 2025-09-07T07:04:17.0075889Z """ 2025-09-07T07:04:17.0076338Z  2025-09-07T07:04:17.0076760Z import json 2025-09-07T07:04:17.0077210Z import logging 2025-09-07T07:04:17.0077796Z import os 2025-09-07T07:04:17.0078223Z import random 2025-09-07T07:04:17.0078667Z import re 2025-09-07T07:04:17.0079080Z import sys 2025-09-07T07:04:17.0079554Z from argparse import ArgumentParser 2025-09-07T07:04:17.0080255Z from collections.abc import Iterable 2025-09-07T07:04:17.0080857Z from functools import cache 2025-09-07T07:04:17.0081398Z from logging import LogRecord 2025-09-07T07:04:17.0081972Z from typing import Any, NamedTuple 2025-09-07T07:04:17.0082603Z from urllib.request import Request, urlopen 2025-09-07T07:04:17.0083192Z  2025-09-07T07:04:17.0083571Z import yaml 2025-09-07T07:04:17.0084041Z from github import Auth, Github 2025-09-07T07:04:17.0084601Z from github.Issue import Issue 2025-09-07T07:04:17.0085112Z  2025-09-07T07:04:17.0085476Z  2025-09-07T07:04:17.0085945Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T07:04:17.0086709Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T07:04:17.0087781Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T07:04:17.0088533Z  2025-09-07T07:04:17.0089229Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T07:04:17.0089870Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T07:04:17.0090454Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T07:04:17.0091089Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T07:04:17.0091655Z  2025-09-07T07:04:17.0092081Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T07:04:17.0092628Z  2025-09-07T07:04:17.0093023Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T07:04:17.0093551Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T07:04:17.0094044Z  2025-09-07T07:04:17.0094410Z  2025-09-07T07:04:17.0094813Z class Experiment(NamedTuple): 2025-09-07T07:04:17.0095368Z  rollout_perc: float = ( 2025-09-07T07:04:17.0096100Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T07:04:17.0096830Z  ) 2025-09-07T07:04:17.0097351Z  all_branches: bool = ( 2025-09-07T07:04:17.0098095Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T07:04:17.0098819Z  ) 2025-09-07T07:04:17.0099226Z  default: bool = ( 2025-09-07T07:04:17.0099891Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T07:04:17.0100597Z  ) 2025-09-07T07:04:17.0100987Z  2025-09-07T07:04:17.0101395Z  # Add more fields as needed 2025-09-07T07:04:17.0101905Z  2025-09-07T07:04:17.0102270Z  2025-09-07T07:04:17.0102669Z class Settings(NamedTuple): 2025-09-07T07:04:17.0103175Z  """ 2025-09-07T07:04:17.0103706Z  Settings for the experiments that can be opted into. 2025-09-07T07:04:17.0104320Z  """ 2025-09-07T07:04:17.0104723Z  2025-09-07T07:04:17.0105213Z  experiments: dict[str, Experiment] = {} 2025-09-07T07:04:17.0105775Z  2025-09-07T07:04:17.0106288Z  2025-09-07T07:04:17.0106765Z class ColorFormatter(logging.Formatter): 2025-09-07T07:04:17.0107755Z  """Color codes the log messages based on the log level""" 2025-09-07T07:04:17.0108388Z  2025-09-07T07:04:17.0108761Z  COLORS = { 2025-09-07T07:04:17.0109240Z  "WARNING": "\033[33m", # Yellow 2025-09-07T07:04:17.0109810Z  "ERROR": "\033[31m", # Red 2025-09-07T07:04:17.0110358Z  "CRITICAL": "\033[31m", # Red 2025-09-07T07:04:17.0110913Z  "INFO": "\033[0m", # Reset 2025-09-07T07:04:17.0111455Z  "DEBUG": "\033[0m", # Reset 2025-09-07T07:04:17.0111971Z  } 2025-09-07T07:04:17.0112354Z  2025-09-07T07:04:17.0112811Z  def format(self, record: LogRecord) -> str: 2025-09-07T07:04:17.0113634Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T07:04:17.0114483Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T07:04:17.0115119Z  return super().format(record) 2025-09-07T07:04:17.0115647Z  2025-09-07T07:04:17.0116014Z  2025-09-07T07:04:17.0116432Z handler = logging.StreamHandler() 2025-09-07T07:04:17.0117228Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T07:04:17.0118185Z  2025-09-07T07:04:17.0118689Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T07:04:17.0119334Z log.addHandler(handler) 2025-09-07T07:04:17.0119849Z log.setLevel(logging.INFO) 2025-09-07T07:04:17.0120346Z  2025-09-07T07:04:17.0120711Z  2025-09-07T07:04:17.0121208Z def set_github_output(key: str, value: str) -> None: 2025-09-07T07:04:17.0121828Z  """ 2025-09-07T07:04:17.0122409Z  Defines outputs of the github action that invokes this script 2025-09-07T07:04:17.0123234Z  """ 2025-09-07T07:04:17.0123650Z  if not GITHUB_OUTPUT: 2025-09-07T07:04:17.0124802Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T07:04:17.0125985Z  log.warning( 2025-09-07T07:04:17.0126937Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T07:04:17.0128058Z  ) 2025-09-07T07:04:17.0128558Z  print(f"::set-output name={key}::{value}") 2025-09-07T07:04:17.0129147Z  return 2025-09-07T07:04:17.0129578Z  2025-09-07T07:04:17.0130004Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T07:04:17.0130634Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T07:04:17.0131290Z  f.write(f"{key}={value}\n") 2025-09-07T07:04:17.0131820Z  2025-09-07T07:04:17.0132189Z  2025-09-07T07:04:17.0132747Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T07:04:17.0133455Z  return frozenset( 2025-09-07T07:04:17.0134170Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T07:04:17.0134885Z  ) 2025-09-07T07:04:17.0135281Z  2025-09-07T07:04:17.0135647Z  2025-09-07T07:04:17.0136043Z def parse_args() -> Any: 2025-09-07T07:04:17.0136694Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T07:04:17.0137740Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T07:04:17.0138564Z  parser.add_argument( 2025-09-07T07:04:17.0139093Z  "--github-issue-repo", 2025-09-07T07:04:17.0139637Z  type=str, 2025-09-07T07:04:17.0140115Z  required=False, 2025-09-07T07:04:17.0140780Z  default="pytorch/test-infra", 2025-09-07T07:04:17.0141400Z  help="GitHub repo to get the issue", 2025-09-07T07:04:17.0141965Z  ) 2025-09-07T07:04:17.0142385Z  parser.add_argument( 2025-09-07T07:04:17.0142905Z  "--github-repo", 2025-09-07T07:04:17.0143414Z  type=str, 2025-09-07T07:04:17.0143891Z  required=True, 2025-09-07T07:04:17.0144448Z  help="GitHub repo where CI is running", 2025-09-07T07:04:17.0145008Z  ) 2025-09-07T07:04:17.0145429Z  parser.add_argument( 2025-09-07T07:04:17.0146125Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T07:04:17.0146835Z  ) 2025-09-07T07:04:17.0147801Z  parser.add_argument( 2025-09-07T07:04:17.0148558Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T07:04:17.0149304Z  ) 2025-09-07T07:04:17.0149719Z  parser.add_argument( 2025-09-07T07:04:17.0150458Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T07:04:17.0151193Z  ) 2025-09-07T07:04:17.0151620Z  parser.add_argument( 2025-09-07T07:04:17.0152389Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T07:04:17.0153153Z  ) 2025-09-07T07:04:17.0153606Z  parser.add_argument( 2025-09-07T07:04:17.0154130Z  "--github-ref-type", 2025-09-07T07:04:17.0154660Z  type=str, 2025-09-07T07:04:17.0155140Z  required=True, 2025-09-07T07:04:17.0155734Z  help="Current GitHub ref type, branch or tag", 2025-09-07T07:04:17.0156334Z  ) 2025-09-07T07:04:17.0156756Z  parser.add_argument( 2025-09-07T07:04:17.0157429Z  "--eligible-experiments", 2025-09-07T07:04:17.0158198Z  type=_str_comma_separated_to_set, 2025-09-07T07:04:17.0158774Z  required=False, 2025-09-07T07:04:17.0159276Z  default="", 2025-09-07T07:04:17.0160211Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T07:04:17.0161184Z  ) 2025-09-07T07:04:17.0161619Z  parser.add_argument( 2025-09-07T07:04:17.0162159Z  "--opt-out-experiments", 2025-09-07T07:04:17.0162746Z  type=_str_comma_separated_to_set, 2025-09-07T07:04:17.0163571Z  required=False, 2025-09-07T07:04:17.0164173Z  default="", 2025-09-07T07:04:17.0164648Z  help=( 2025-09-07T07:04:17.0165407Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T07:04:17.0166611Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T07:04:17.0167633Z  ), 2025-09-07T07:04:17.0168058Z  ) 2025-09-07T07:04:17.0168490Z  parser.add_argument( 2025-09-07T07:04:17.0169016Z  "--pr-number", 2025-09-07T07:04:17.0169512Z  type=str, 2025-09-07T07:04:17.0169988Z  required=False, 2025-09-07T07:04:17.0170482Z  default="", 2025-09-07T07:04:17.0171045Z  help="the optional PR number where this is run", 2025-09-07T07:04:17.0171651Z  ) 2025-09-07T07:04:17.0172042Z  2025-09-07T07:04:17.0172464Z  return parser.parse_args() 2025-09-07T07:04:17.0172982Z  2025-09-07T07:04:17.0173349Z  2025-09-07T07:04:17.0173997Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T07:04:17.0174965Z  auth = Auth.Token(github_token) 2025-09-07T07:04:17.0175571Z  return Github(auth=auth) 2025-09-07T07:04:17.0176075Z  2025-09-07T07:04:17.0176448Z  2025-09-07T07:04:17.0177152Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T07:04:17.0178132Z  repo = gh.get_repo(repo) 2025-09-07T07:04:17.0178709Z  return repo.get_issue(number=issue_num) 2025-09-07T07:04:17.0179284Z  2025-09-07T07:04:17.0179654Z  2025-09-07T07:04:17.0180066Z def get_potential_pr_author( 2025-09-07T07:04:17.0180801Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T07:04:17.0181524Z ) -> str: 2025-09-07T07:04:17.0182122Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T07:04:17.0182992Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T07:04:17.0183824Z  # embedded in the tag name: ciflow// 2025-09-07T07:04:17.0184442Z  2025-09-07T07:04:17.0184866Z  gh = get_gh_client(github_token) 2025-09-07T07:04:17.0185401Z  2025-09-07T07:04:17.0185908Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T07:04:17.0186587Z  split_tag = ref_name.split("/") 2025-09-07T07:04:17.0187130Z  if ( 2025-09-07T07:04:17.0187686Z  len(split_tag) == 3 2025-09-07T07:04:17.0188242Z  and split_tag[0] == "ciflow" 2025-09-07T07:04:17.0188828Z  and split_tag[2].isnumeric() 2025-09-07T07:04:17.0189374Z  ): 2025-09-07T07:04:17.0189830Z  pr_number = split_tag[2] 2025-09-07T07:04:17.0190370Z  try: 2025-09-07T07:04:17.0190880Z  repository = gh.get_repo(repo) 2025-09-07T07:04:17.0191707Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T07:04:17.0192367Z  except Exception as e: 2025-09-07T07:04:17.0192969Z  raise Exception( # noqa: TRY002 2025-09-07T07:04:17.0193703Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T07:04:17.0194391Z  ) from e 2025-09-07T07:04:17.0195107Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T07:04:17.0196039Z  # In all other cases, return the original input username 2025-09-07T07:04:17.0196724Z  return username 2025-09-07T07:04:17.0197192Z  2025-09-07T07:04:17.0197671Z  2025-09-07T07:04:17.0198136Z def is_exception_branch(branch: str) -> bool: 2025-09-07T07:04:17.0198715Z  """ 2025-09-07T07:04:17.0199443Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T07:04:17.0200273Z  """ 2025-09-07T07:04:17.0200892Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T07:04:17.0201595Z  2025-09-07T07:04:17.0201967Z  2025-09-07T07:04:17.0202401Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T07:04:17.0202942Z  try: 2025-09-07T07:04:17.0203406Z  data = yaml.safe_load(yaml_text) 2025-09-07T07:04:17.0203959Z  return data 2025-09-07T07:04:17.0204452Z  except yaml.YAMLError: 2025-09-07T07:04:17.0205009Z  log.exception("Error loading YAML") 2025-09-07T07:04:17.0205563Z  raise 2025-09-07T07:04:17.0205989Z  2025-09-07T07:04:17.0206359Z  2025-09-07T07:04:17.0207012Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T07:04:17.0207898Z  """ 2025-09-07T07:04:17.0208712Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T07:04:17.0209511Z  2025-09-07T07:04:17.0210089Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T07:04:17.0210924Z  and the text below is the list of opted in users. 2025-09-07T07:04:17.0211525Z  2025-09-07T07:04:17.0212135Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T07:04:17.0212874Z  """ 2025-09-07T07:04:17.0213388Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T07:04:17.0214040Z  if len(rollout_state_parts) >= 2: 2025-09-07T07:04:17.0214716Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T07:04:17.0215350Z  else: 2025-09-07T07:04:17.0215792Z  return "", rollout_state 2025-09-07T07:04:17.0216304Z  2025-09-07T07:04:17.0216677Z  2025-09-07T07:04:17.0217118Z class UserOptins(dict[str, list[str]]): 2025-09-07T07:04:17.0217775Z  """ 2025-09-07T07:04:17.0218360Z  Dictionary of users with a list of features they have opted into 2025-09-07T07:04:17.0219044Z  """ 2025-09-07T07:04:17.0219431Z  2025-09-07T07:04:17.0219794Z  2025-09-07T07:04:17.0220371Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T07:04:17.0221066Z  """ 2025-09-07T07:04:17.0221839Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T07:04:17.0222705Z  2025-09-07T07:04:17.0223551Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T07:04:17.0224602Z  - Example line: "@User1,lf,split_build" 2025-09-07T07:04:17.0225488Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T07:04:17.0226155Z  2025-09-07T07:04:17.0226521Z  2025-09-07T07:04:17.0226887Z  """ 2025-09-07T07:04:17.0227416Z  optins = UserOptins() 2025-09-07T07:04:17.0227985Z  for user in user_optin_text.split("\n"): 2025-09-07T07:04:17.0228595Z  user = user.strip("\r\n\t -") 2025-09-07T07:04:17.0229201Z  if not user or not user.startswith("@"): 2025-09-07T07:04:17.0229801Z  # Not a valid user. Skip 2025-09-07T07:04:17.0230335Z  continue 2025-09-07T07:04:17.0230791Z  2025-09-07T07:04:17.0231168Z  if user: 2025-09-07T07:04:17.0231715Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T07:04:17.0232461Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T07:04:17.0233144Z  2025-09-07T07:04:17.0233538Z  return optins 2025-09-07T07:04:17.0233991Z  2025-09-07T07:04:17.0234350Z  2025-09-07T07:04:17.0234885Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T07:04:17.0235541Z  """ 2025-09-07T07:04:17.0236006Z  Check if the experiment name is valid. 2025-09-07T07:04:17.0236569Z  A valid name: 2025-09-07T07:04:17.0237406Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T07:04:17.0238404Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T07:04:17.0239169Z  - Cannot contain spaces 2025-09-07T07:04:17.0239687Z  """ 2025-09-07T07:04:17.0240071Z  2025-09-07T07:04:17.0240567Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T07:04:17.0241341Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T07:04:17.0242112Z  2025-09-07T07:04:17.0242493Z  if valid: 2025-09-07T07:04:17.0243038Z  return True 2025-09-07T07:04:17.0243658Z  2025-09-07T07:04:17.0244036Z  log.error( 2025-09-07T07:04:17.0245562Z  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-07T07:04:17.0247154Z  ) 2025-09-07T07:04:17.0247675Z  return False 2025-09-07T07:04:17.0248126Z  2025-09-07T07:04:17.0248493Z  2025-09-07T07:04:17.0249050Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T07:04:17.0249732Z  """ 2025-09-07T07:04:17.0250386Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T07:04:17.0251150Z  """ 2025-09-07T07:04:17.0251551Z  try: 2025-09-07T07:04:17.0251967Z  if settings_text: 2025-09-07T07:04:17.0252769Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T07:04:17.0253610Z  # for easy reading 2025-09-07T07:04:17.0254479Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T07:04:17.0255430Z  # the backtick character in shell commands. 2025-09-07T07:04:17.0256089Z  backtick = chr(96) # backtick character 2025-09-07T07:04:17.0256829Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T07:04:17.0257665Z  settings = load_yaml(settings_text) 2025-09-07T07:04:17.0258216Z  2025-09-07T07:04:17.0258865Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T07:04:17.0259839Z  experiments = {} 2025-09-07T07:04:17.0260340Z  2025-09-07T07:04:17.0260942Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T07:04:17.0261767Z  if not is_valid_experiment_name(exp_name): 2025-09-07T07:04:17.0262917Z  # 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-07T07:04:17.0263999Z  continue 2025-09-07T07:04:17.0264491Z  2025-09-07T07:04:17.0264902Z  valid_settings = {} 2025-09-07T07:04:17.0265489Z  for setting in exp_settings: 2025-09-07T07:04:17.0266110Z  if setting not in Experiment._fields: 2025-09-07T07:04:17.0266721Z  log.warning( 2025-09-07T07:04:17.0267639Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T07:04:17.0268388Z  ) 2025-09-07T07:04:17.0268874Z  else: 2025-09-07T07:04:17.0269459Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T07:04:17.0270067Z  2025-09-07T07:04:17.0270579Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T07:04:17.0271264Z  return Settings(experiments) 2025-09-07T07:04:17.0271798Z  2025-09-07T07:04:17.0272187Z  except Exception: 2025-09-07T07:04:17.0272746Z  log.exception("Failed to parse settings") 2025-09-07T07:04:17.0273316Z  2025-09-07T07:04:17.0273704Z  return Settings() 2025-09-07T07:04:17.0274164Z  2025-09-07T07:04:17.0274525Z  2025-09-07T07:04:17.0275149Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T07:04:17.0275777Z  """ 2025-09-07T07:04:17.0276279Z  Parse settings, if any, from the rollout state. 2025-09-07T07:04:17.0276867Z  2025-09-07T07:04:17.0277558Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T07:04:17.0278375Z  and the text below is the list of opted in users. 2025-09-07T07:04:17.0278973Z  2025-09-07T07:04:17.0279610Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T07:04:17.0280379Z  """ 2025-09-07T07:04:17.0280992Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:04:17.0281803Z  return parse_settings_from_text(settings_text) 2025-09-07T07:04:17.0282397Z  2025-09-07T07:04:17.0282749Z  2025-09-07T07:04:17.0283248Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T07:04:17.0283856Z  """ 2025-09-07T07:04:17.0284301Z  Parse users from the rollout state. 2025-09-07T07:04:17.0284848Z  2025-09-07T07:04:17.0285208Z  """ 2025-09-07T07:04:17.0285810Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:04:17.0286607Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T07:04:17.0287209Z  2025-09-07T07:04:17.0287666Z  2025-09-07T07:04:17.0288341Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:04:17.0289136Z  """ 2025-09-07T07:04:17.0289614Z  Check if a user is opted into an experiment 2025-09-07T07:04:17.0290190Z  """ 2025-09-07T07:04:17.0290711Z  return experiment_name in user_optins.get(user, []) 2025-09-07T07:04:17.0291332Z  2025-09-07T07:04:17.0291836Z  2025-09-07T07:04:17.0292511Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:04:17.0293312Z  """ 2025-09-07T07:04:17.0293834Z  Check if a user explicitly opted out of an experiment 2025-09-07T07:04:17.0294456Z  """ 2025-09-07T07:04:17.0295023Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T07:04:17.0295771Z  experiment_optout = "-" + experiment_name 2025-09-07T07:04:17.0296476Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T07:04:17.0297130Z  return False 2025-09-07T07:04:17.0297692Z  2025-09-07T07:04:17.0298192Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T07:04:17.0298841Z  log.warning( 2025-09-07T07:04:17.0299737Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T07:04:17.0300655Z  ) 2025-09-07T07:04:17.0301050Z  2025-09-07T07:04:17.0301428Z  return True 2025-09-07T07:04:17.0301857Z  2025-09-07T07:04:17.0302220Z  2025-09-07T07:04:17.0302609Z def get_runner_prefix( 2025-09-07T07:04:17.0303109Z  rollout_state: str, 2025-09-07T07:04:17.0303648Z  workflow_requestors: Iterable[str], 2025-09-07T07:04:17.0304205Z  branch: str, 2025-09-07T07:04:17.0304777Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T07:04:17.0305507Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T07:04:17.0306140Z  is_canary: bool = False, 2025-09-07T07:04:17.0306640Z ) -> str: 2025-09-07T07:04:17.0307130Z  settings = parse_settings(rollout_state) 2025-09-07T07:04:17.0307871Z  user_optins = parse_users(rollout_state) 2025-09-07T07:04:17.0308430Z  2025-09-07T07:04:17.0308940Z  fleet_prefix = "" 2025-09-07T07:04:17.0309435Z  prefixes = [] 2025-09-07T07:04:17.0310148Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T07:04:17.0311150Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T07:04:17.0311906Z  log.info( 2025-09-07T07:04:17.0312659Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T07:04:17.0313441Z  ) 2025-09-07T07:04:17.0313887Z  continue 2025-09-07T07:04:17.0314330Z  2025-09-07T07:04:17.0314734Z  if opt_out_experiments: 2025-09-07T07:04:17.0315340Z  if experiment_name in opt_out_experiments: 2025-09-07T07:04:17.0316064Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T07:04:17.0316708Z  log.info( 2025-09-07T07:04:17.0317808Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T07:04:17.0318830Z  ) 2025-09-07T07:04:17.0319286Z  continue 2025-09-07T07:04:17.0319766Z  2025-09-07T07:04:17.0320178Z  if eligible_experiments: 2025-09-07T07:04:17.0320811Z  if experiment_name not in eligible_experiments: 2025-09-07T07:04:17.0321498Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T07:04:17.0322089Z  log.info( 2025-09-07T07:04:17.0322946Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T07:04:17.0323809Z  ) 2025-09-07T07:04:17.0324268Z  continue 2025-09-07T07:04:17.0324960Z  elif not experiment_settings.default: 2025-09-07T07:04:17.0325530Z  log.info( 2025-09-07T07:04:17.0326264Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T07:04:17.0327030Z  ) 2025-09-07T07:04:17.0327574Z  continue 2025-09-07T07:04:17.0328020Z  2025-09-07T07:04:17.0328530Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T07:04:17.0329197Z  opted_out_users = [ 2025-09-07T07:04:17.0329706Z  requestor 2025-09-07T07:04:17.0330235Z  for requestor in workflow_requestors 2025-09-07T07:04:17.0330964Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T07:04:17.0331636Z  ] 2025-09-07T07:04:17.0332036Z  2025-09-07T07:04:17.0332429Z  if opted_out_users: 2025-09-07T07:04:17.0332998Z  log.info( 2025-09-07T07:04:17.0333703Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T07:04:17.0334441Z  ) 2025-09-07T07:04:17.0334880Z  continue 2025-09-07T07:04:17.0335328Z  2025-09-07T07:04:17.0335824Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T07:04:17.0336480Z  opted_in_users = [ 2025-09-07T07:04:17.0336991Z  requestor 2025-09-07T07:04:17.0337628Z  for requestor in workflow_requestors 2025-09-07T07:04:17.0338354Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T07:04:17.0339012Z  ] 2025-09-07T07:04:17.0339414Z  2025-09-07T07:04:17.0339800Z  enabled = False 2025-09-07T07:04:17.0340308Z  if opted_in_users: 2025-09-07T07:04:17.0340960Z  log.info( 2025-09-07T07:04:17.0341659Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T07:04:17.0342382Z  ) 2025-09-07T07:04:17.0342832Z  enabled = True 2025-09-07T07:04:17.0343318Z  2025-09-07T07:04:17.0343762Z  elif experiment_settings.rollout_perc: 2025-09-07T07:04:17.0344654Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T07:04:17.0345648Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T07:04:17.0346340Z  log.info( 2025-09-07T07:04:17.0347386Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T07:04:17.0348349Z  ) 2025-09-07T07:04:17.0348841Z  enabled = True 2025-09-07T07:04:17.0349343Z  2025-09-07T07:04:17.0349732Z  if enabled: 2025-09-07T07:04:17.0350226Z  label = experiment_name 2025-09-07T07:04:17.0350846Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T07:04:17.0351727Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T07:04:17.0352652Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T07:04:17.0353471Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T07:04:17.0354176Z  if is_canary: 2025-09-07T07:04:17.0354744Z  label += CANARY_FLEET_SUFFIX 2025-09-07T07:04:17.0355339Z  fleet_prefix = label 2025-09-07T07:04:17.0355876Z  else: 2025-09-07T07:04:17.0356519Z  prefixes.append(label) 2025-09-07T07:04:17.0357065Z  2025-09-07T07:04:17.0357565Z  if len(prefixes) > 1: 2025-09-07T07:04:17.0358072Z  log.error( 2025-09-07T07:04:17.0359189Z  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-07T07:04:17.0360347Z  ) 2025-09-07T07:04:17.0360791Z  prefixes = prefixes[:1] 2025-09-07T07:04:17.0361311Z  2025-09-07T07:04:17.0361706Z  # Fleet always comes first 2025-09-07T07:04:17.0362237Z  if fleet_prefix: 2025-09-07T07:04:17.0362756Z  prefixes.insert(0, fleet_prefix) 2025-09-07T07:04:17.0363303Z  2025-09-07T07:04:17.0363800Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T07:04:17.0364397Z  2025-09-07T07:04:17.0364768Z  2025-09-07T07:04:17.0365442Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T07:04:17.0366255Z  """ 2025-09-07T07:04:17.0366894Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T07:04:17.0367771Z  2025-09-07T07:04:17.0368393Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T07:04:17.0369153Z  """ 2025-09-07T07:04:17.0369612Z  gh = get_gh_client(github_token) 2025-09-07T07:04:17.0370220Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T07:04:17.0370937Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T07:04:17.0371571Z  2025-09-07T07:04:17.0371942Z  2025-09-07T07:04:17.0372576Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T07:04:17.0373518Z  for _ in range(num_retries): 2025-09-07T07:04:17.0374055Z  try: 2025-09-07T07:04:17.0374547Z  req = Request(url=url, headers=headers) 2025-09-07T07:04:17.0375267Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T07:04:17.0375960Z  return json.loads(content) 2025-09-07T07:04:17.0376539Z  except Exception as e: 2025-09-07T07:04:17.0377161Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T07:04:17.0377871Z  2025-09-07T07:04:17.0378501Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T07:04:17.0379253Z  return {} 2025-09-07T07:04:17.0379685Z  2025-09-07T07:04:17.0380048Z  2025-09-07T07:04:17.0380426Z @cache 2025-09-07T07:04:17.0381120Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T07:04:17.0381939Z  """ 2025-09-07T07:04:17.0382389Z  Dynamically get PR information 2025-09-07T07:04:17.0382918Z  """ 2025-09-07T07:04:17.0383480Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T07:04:17.0384149Z  headers = { 2025-09-07T07:04:17.0384684Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T07:04:17.0385347Z  "Authorization": f"token {github_token}", 2025-09-07T07:04:17.0385926Z  } 2025-09-07T07:04:17.0386409Z  json_response: dict[str, Any] = download_json( 2025-09-07T07:04:17.0387077Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T07:04:17.0387765Z  headers=headers, 2025-09-07T07:04:17.0388248Z  ) 2025-09-07T07:04:17.0388634Z  2025-09-07T07:04:17.0389024Z  if not json_response: 2025-09-07T07:04:17.0389674Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T07:04:17.0390472Z  return {} 2025-09-07T07:04:17.0390921Z  2025-09-07T07:04:17.0391313Z  return json_response 2025-09-07T07:04:17.0391803Z  2025-09-07T07:04:17.0392165Z  2025-09-07T07:04:17.0392792Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T07:04:17.0393557Z  """ 2025-09-07T07:04:17.0394144Z  Dynamically get the latest list of labels from the pull request 2025-09-07T07:04:17.0394834Z  """ 2025-09-07T07:04:17.0395375Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T07:04:17.0396021Z  return { 2025-09-07T07:04:17.0396682Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T07:04:17.0397514Z  } 2025-09-07T07:04:17.0397908Z  2025-09-07T07:04:17.0398272Z  2025-09-07T07:04:17.0398669Z def main() -> None: 2025-09-07T07:04:17.0399149Z  args = parse_args() 2025-09-07T07:04:17.0399638Z  2025-09-07T07:04:17.0400091Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T07:04:17.0400674Z  2025-09-07T07:04:17.0401087Z  # Check if the PR is opt-out 2025-09-07T07:04:17.0401626Z  if args.pr_number: 2025-09-07T07:04:17.0402366Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T07:04:17.0403161Z  if OPT_OUT_LABEL in labels: 2025-09-07T07:04:17.0403708Z  log.info( 2025-09-07T07:04:17.0404467Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T07:04:17.0405269Z  ) 2025-09-07T07:04:17.0405907Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:04:17.0406641Z  sys.exit() 2025-09-07T07:04:17.0407231Z  2025-09-07T07:04:17.0407719Z  try: 2025-09-07T07:04:17.0408222Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T07:04:17.0408986Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T07:04:17.0409669Z  ) 2025-09-07T07:04:17.0410076Z  2025-09-07T07:04:17.0410512Z  username = get_potential_pr_author( 2025-09-07T07:04:17.0411101Z  args.github_token, 2025-09-07T07:04:17.0411637Z  args.github_repo, 2025-09-07T07:04:17.0412243Z  args.github_actor, 2025-09-07T07:04:17.0412793Z  args.github_ref_type, 2025-09-07T07:04:17.0413352Z  args.github_branch, 2025-09-07T07:04:17.0413861Z  ) 2025-09-07T07:04:17.0414269Z  2025-09-07T07:04:17.0414798Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T07:04:17.0415450Z  2025-09-07T07:04:17.0415903Z  runner_label_prefix = get_runner_prefix( 2025-09-07T07:04:17.0416496Z  rollout_state, 2025-09-07T07:04:17.0417063Z  (args.github_issue_owner, username), 2025-09-07T07:04:17.0417751Z  args.github_branch, 2025-09-07T07:04:17.0418320Z  args.eligible_experiments, 2025-09-07T07:04:17.0418912Z  args.opt_out_experiments, 2025-09-07T07:04:17.0419454Z  is_canary, 2025-09-07T07:04:17.0419926Z  ) 2025-09-07T07:04:17.0420319Z  2025-09-07T07:04:17.0420722Z  except Exception as e: 2025-09-07T07:04:17.0421237Z  log.error( 2025-09-07T07:04:17.0421997Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T07:04:17.0422813Z  ) 2025-09-07T07:04:17.0423356Z  2025-09-07T07:04:17.0423925Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:04:17.0424611Z  2025-09-07T07:04:17.0424981Z  2025-09-07T07:04:17.0425370Z if __name__ == "__main__": 2025-09-07T07:04:17.0425874Z  main() 2025-09-07T07:04:17.0426282Z  2025-09-07T07:04:17.0426655Z EOF 2025-09-07T07:04:17.0427032Z  2025-09-07T07:04:17.0427541Z cat runner_determinator.py 2025-09-07T07:04:17.0695536Z shell: /usr/bin/bash -e {0} 2025-09-07T07:04:17.0696402Z env: 2025-09-07T07:04:17.0697188Z GITHUB_TOKEN: *** 2025-09-07T07:04:17.0697871Z ISSUE_NUMBER: 5132 2025-09-07T07:04:17.0698349Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:04:17.0698899Z ISSUE_OWNER: 2025-09-07T07:04:17.0699323Z CHECK_EXPERIMENTS: 2025-09-07T07:04:17.0699793Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:04:17.0700263Z PR_NUMBER: 2025-09-07T07:04:17.0700711Z ##[endgroup] 2025-09-07T07:04:17.0906490Z # flake8: noqa: G004 2025-09-07T07:04:17.0906853Z 2025-09-07T07:04:17.0907742Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T07:04:17.0908740Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T07:04:17.0909568Z # python .github/scripts/update_runner_determinator.py 2025-09-07T07:04:17.0910023Z 2025-09-07T07:04:17.0910189Z """ 2025-09-07T07:04:17.0910774Z This runner determinator is used to determine which set of runners to run a 2025-09-07T07:04:17.0911649Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T07:04:17.0912546Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T07:04:17.0913358Z of which runners should be used to run which job. 2025-09-07T07:04:17.0913759Z 2025-09-07T07:04:17.0914144Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T07:04:17.0915244Z separated by a line containing "---". If the line is not present, the 2025-09-07T07:04:17.0916140Z settings are considered to be empty with only the second part, the user 2025-09-07T07:04:17.0916835Z list, defined. 2025-09-07T07:04:17.0917073Z 2025-09-07T07:04:17.0917760Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T07:04:17.0918711Z used to define any settings that are needed to determine which runners to use. 2025-09-07T07:04:17.0919543Z It's fields are defined by the RolloutSettings class below. 2025-09-07T07:04:17.0919991Z 2025-09-07T07:04:17.0920372Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T07:04:17.0921234Z The user list is also a comma separated list of additional features or 2025-09-07T07:04:17.0921976Z experiments which the user could be opted in to. 2025-09-07T07:04:17.0922378Z 2025-09-07T07:04:17.0922585Z The user list has the following rules: 2025-09-07T07:04:17.0922941Z 2025-09-07T07:04:17.0923269Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T07:04:17.0924143Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T07:04:17.0924931Z - A "#" prefix opts the user out of all experiments 2025-09-07T07:04:17.0925327Z 2025-09-07T07:04:17.0925522Z Example config: 2025-09-07T07:04:17.0925985Z # A list of experiments that can be opted into. 2025-09-07T07:04:17.0926664Z # This defines the behavior they'll induce when opted into. 2025-09-07T07:04:17.0927486Z # Expected syntax is: 2025-09-07T07:04:17.0928165Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T07:04:17.0929134Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T07:04:17.0929744Z 2025-09-07T07:04:17.0929925Z experiments: 2025-09-07T07:04:17.0930329Z lf: 2025-09-07T07:04:17.0930719Z rollout_percent: 25 2025-09-07T07:04:17.0931192Z all_branches: false 2025-09-07T07:04:17.0931820Z default: true 2025-09-07T07:04:17.0932246Z --- 2025-09-07T07:04:17.0932452Z 2025-09-07T07:04:17.0932620Z # Opt-ins: 2025-09-07T07:04:17.0933208Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T07:04:17.0934069Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T07:04:17.0934861Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T07:04:17.0935526Z # Experiments should be from the above list. 2025-09-07T07:04:17.0935911Z 2025-09-07T07:04:17.0936097Z @User1,-lf,split_build 2025-09-07T07:04:17.0936544Z @User2,lf 2025-09-07T07:04:17.0936933Z @User3,split_build 2025-09-07T07:04:17.0937778Z """ 2025-09-07T07:04:17.0938007Z 2025-09-07T07:04:17.0938185Z import json 2025-09-07T07:04:17.0938579Z import logging 2025-09-07T07:04:17.0938967Z import os 2025-09-07T07:04:17.0939371Z import random 2025-09-07T07:04:17.0939759Z import re 2025-09-07T07:04:17.0940143Z import sys 2025-09-07T07:04:17.0940558Z from argparse import ArgumentParser 2025-09-07T07:04:17.0941099Z from collections.abc import Iterable 2025-09-07T07:04:17.0941643Z from functools import cache 2025-09-07T07:04:17.0942129Z from logging import LogRecord 2025-09-07T07:04:17.0942635Z from typing import Any, NamedTuple 2025-09-07T07:04:17.0943171Z from urllib.request import Request, urlopen 2025-09-07T07:04:17.0943551Z 2025-09-07T07:04:17.0943724Z import yaml 2025-09-07T07:04:17.0944122Z from github import Auth, Github 2025-09-07T07:04:17.0944624Z from github.Issue import Issue 2025-09-07T07:04:17.0944925Z 2025-09-07T07:04:17.0944931Z 2025-09-07T07:04:17.0945155Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T07:04:17.0945842Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T07:04:17.0946709Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T07:04:17.0947487Z 2025-09-07T07:04:17.0947740Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T07:04:17.0948478Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T07:04:17.0949014Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T07:04:17.0949579Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T07:04:17.0949938Z 2025-09-07T07:04:17.0950145Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T07:04:17.0950484Z 2025-09-07T07:04:17.0950676Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T07:04:17.0951159Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T07:04:17.0951439Z 2025-09-07T07:04:17.0951445Z 2025-09-07T07:04:17.0951636Z class Experiment(NamedTuple): 2025-09-07T07:04:17.0952126Z rollout_perc: float = ( 2025-09-07T07:04:17.0952763Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T07:04:17.0953454Z ) 2025-09-07T07:04:17.0953843Z all_branches: bool = ( 2025-09-07T07:04:17.0954466Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T07:04:17.0955147Z ) 2025-09-07T07:04:17.0955519Z default: bool = ( 2025-09-07T07:04:17.0956102Z True # If True, the experiment is enabled by default for all queries 2025-09-07T07:04:17.0956746Z ) 2025-09-07T07:04:17.0956957Z 2025-09-07T07:04:17.0957145Z # Add more fields as needed 2025-09-07T07:04:17.0957681Z 2025-09-07T07:04:17.0957689Z 2025-09-07T07:04:17.0957894Z class Settings(NamedTuple): 2025-09-07T07:04:17.0958342Z """ 2025-09-07T07:04:17.0958807Z Settings for the experiments that can be opted into. 2025-09-07T07:04:17.0959386Z """ 2025-09-07T07:04:17.0959585Z 2025-09-07T07:04:17.0959800Z experiments: dict[str, Experiment] = {} 2025-09-07T07:04:17.0960165Z 2025-09-07T07:04:17.0960171Z 2025-09-07T07:04:17.0960383Z class ColorFormatter(logging.Formatter): 2025-09-07T07:04:17.0961007Z """Color codes the log messages based on the log level""" 2025-09-07T07:04:17.0961435Z 2025-09-07T07:04:17.0961603Z COLORS = { 2025-09-07T07:04:17.0962002Z "WARNING": "\033[33m", # Yellow 2025-09-07T07:04:17.0962695Z "ERROR": "\033[31m", # Red 2025-09-07T07:04:17.0963192Z "CRITICAL": "\033[31m", # Red 2025-09-07T07:04:17.0963699Z "INFO": "\033[0m", # Reset 2025-09-07T07:04:17.0964188Z "DEBUG": "\033[0m", # Reset 2025-09-07T07:04:17.0964669Z } 2025-09-07T07:04:17.0964877Z 2025-09-07T07:04:17.0965103Z def format(self, record: LogRecord) -> str: 2025-09-07T07:04:17.0965859Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T07:04:17.0966642Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T07:04:17.0967222Z return super().format(record) 2025-09-07T07:04:17.0967778Z 2025-09-07T07:04:17.0967785Z 2025-09-07T07:04:17.0967999Z handler = logging.StreamHandler() 2025-09-07T07:04:17.0968702Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T07:04:17.0969258Z 2025-09-07T07:04:17.0969505Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T07:04:17.0970087Z log.addHandler(handler) 2025-09-07T07:04:17.0970551Z log.setLevel(logging.INFO) 2025-09-07T07:04:17.0970843Z 2025-09-07T07:04:17.0970849Z 2025-09-07T07:04:17.0971108Z def set_github_output(key: str, value: str) -> None: 2025-09-07T07:04:17.0971666Z """ 2025-09-07T07:04:17.0972174Z Defines outputs of the github action that invokes this script 2025-09-07T07:04:17.0972796Z """ 2025-09-07T07:04:17.0973176Z if not GITHUB_OUTPUT: 2025-09-07T07:04:17.0974234Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T07:04:17.0975349Z log.warning( 2025-09-07T07:04:17.0976198Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T07:04:17.0977111Z ) 2025-09-07T07:04:17.0986932Z print(f"::set-output name={key}::{value}") 2025-09-07T07:04:17.0987812Z return 2025-09-07T07:04:17.0988072Z 2025-09-07T07:04:17.0988502Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T07:04:17.0989151Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T07:04:17.0989740Z f.write(f"{key}={value}\n") 2025-09-07T07:04:17.0990071Z 2025-09-07T07:04:17.0990079Z 2025-09-07T07:04:17.0990393Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T07:04:17.0991043Z return frozenset( 2025-09-07T07:04:17.0991668Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T07:04:17.0992426Z ) 2025-09-07T07:04:17.0992635Z 2025-09-07T07:04:17.0992643Z 2025-09-07T07:04:17.0992828Z def parse_args() -> Any: 2025-09-07T07:04:17.0993392Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T07:04:17.0994271Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T07:04:17.0995057Z parser.add_argument( 2025-09-07T07:04:17.0995527Z "--github-issue-repo", 2025-09-07T07:04:17.0996005Z type=str, 2025-09-07T07:04:17.0996432Z required=False, 2025-09-07T07:04:17.0996910Z default="pytorch/test-infra", 2025-09-07T07:04:17.0997699Z help="GitHub repo to get the issue", 2025-09-07T07:04:17.0998242Z ) 2025-09-07T07:04:17.0998624Z parser.add_argument( 2025-09-07T07:04:17.0999090Z "--github-repo", 2025-09-07T07:04:17.0999542Z type=str, 2025-09-07T07:04:17.0999953Z required=True, 2025-09-07T07:04:17.1000430Z help="GitHub repo where CI is running", 2025-09-07T07:04:17.1000983Z ) 2025-09-07T07:04:17.1001372Z parser.add_argument( 2025-09-07T07:04:17.1001979Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T07:04:17.1002647Z ) 2025-09-07T07:04:17.1003020Z parser.add_argument( 2025-09-07T07:04:17.1003656Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T07:04:17.1004328Z ) 2025-09-07T07:04:17.1004712Z parser.add_argument( 2025-09-07T07:04:17.1005514Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T07:04:17.1006218Z ) 2025-09-07T07:04:17.1006594Z parser.add_argument( 2025-09-07T07:04:17.1007427Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T07:04:17.1008182Z ) 2025-09-07T07:04:17.1008566Z parser.add_argument( 2025-09-07T07:04:17.1009026Z "--github-ref-type", 2025-09-07T07:04:17.1009492Z type=str, 2025-09-07T07:04:17.1009904Z required=True, 2025-09-07T07:04:17.1010401Z help="Current GitHub ref type, branch or tag", 2025-09-07T07:04:17.1010970Z ) 2025-09-07T07:04:17.1011352Z parser.add_argument( 2025-09-07T07:04:17.1011838Z "--eligible-experiments", 2025-09-07T07:04:17.1012370Z type=_str_comma_separated_to_set, 2025-09-07T07:04:17.1012923Z required=False, 2025-09-07T07:04:17.1013356Z default="", 2025-09-07T07:04:17.1014208Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T07:04:17.1015154Z ) 2025-09-07T07:04:17.1015550Z parser.add_argument( 2025-09-07T07:04:17.1016025Z "--opt-out-experiments", 2025-09-07T07:04:17.1016529Z type=_str_comma_separated_to_set, 2025-09-07T07:04:17.1017063Z required=False, 2025-09-07T07:04:17.1017736Z default="", 2025-09-07T07:04:17.1018156Z help=( 2025-09-07T07:04:17.1018842Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T07:04:17.1019976Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T07:04:17.1020816Z ), 2025-09-07T07:04:17.1021184Z ) 2025-09-07T07:04:17.1074675Z parser.add_argument( 2025-09-07T07:04:17.1075670Z "--pr-number", 2025-09-07T07:04:17.1076483Z type=str, 2025-09-07T07:04:17.1076987Z required=False, 2025-09-07T07:04:17.1077696Z default="", 2025-09-07T07:04:17.1078461Z help="the optional PR number where this is run", 2025-09-07T07:04:17.1079071Z ) 2025-09-07T07:04:17.1079276Z 2025-09-07T07:04:17.1079477Z return parser.parse_args() 2025-09-07T07:04:17.1079794Z 2025-09-07T07:04:17.1079801Z 2025-09-07T07:04:17.1080216Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T07:04:17.1080982Z auth = Auth.Token(github_token) 2025-09-07T07:04:17.1081496Z return Github(auth=auth) 2025-09-07T07:04:17.1081800Z 2025-09-07T07:04:17.1081807Z 2025-09-07T07:04:17.1082260Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T07:04:17.1083054Z repo = gh.get_repo(repo) 2025-09-07T07:04:17.1083562Z return repo.get_issue(number=issue_num) 2025-09-07T07:04:17.1083927Z 2025-09-07T07:04:17.1083934Z 2025-09-07T07:04:17.1084138Z def get_potential_pr_author( 2025-09-07T07:04:17.1084782Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T07:04:17.1085462Z ) -> str: 2025-09-07T07:04:17.1085972Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T07:04:17.1086778Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T07:04:17.1087714Z # embedded in the tag name: ciflow// 2025-09-07T07:04:17.1088138Z 2025-09-07T07:04:17.1088331Z gh = get_gh_client(github_token) 2025-09-07T07:04:17.1088667Z 2025-09-07T07:04:17.1088937Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T07:04:17.1089561Z split_tag = ref_name.split("/") 2025-09-07T07:04:17.1090079Z if ( 2025-09-07T07:04:17.1090479Z len(split_tag) == 3 2025-09-07T07:04:17.1090970Z and split_tag[0] == "ciflow" 2025-09-07T07:04:17.1091502Z and split_tag[2].isnumeric() 2025-09-07T07:04:17.1092019Z ): 2025-09-07T07:04:17.1092414Z pr_number = split_tag[2] 2025-09-07T07:04:17.1093153Z try: 2025-09-07T07:04:17.1093593Z repository = gh.get_repo(repo) 2025-09-07T07:04:17.1094206Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T07:04:17.1094812Z except Exception as e: 2025-09-07T07:04:17.1095333Z raise Exception( # noqa: TRY002 2025-09-07T07:04:17.1095996Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T07:04:17.1096638Z ) from e 2025-09-07T07:04:17.1097184Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T07:04:17.1098170Z # In all other cases, return the original input username 2025-09-07T07:04:17.1098766Z return username 2025-09-07T07:04:17.1099011Z 2025-09-07T07:04:17.1099017Z 2025-09-07T07:04:17.1099251Z def is_exception_branch(branch: str) -> bool: 2025-09-07T07:04:17.1099786Z """ 2025-09-07T07:04:17.1100432Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T07:04:17.1101222Z """ 2025-09-07T07:04:17.1101776Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T07:04:17.1102286Z 2025-09-07T07:04:17.1102293Z 2025-09-07T07:04:17.1102500Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T07:04:17.1102993Z try: 2025-09-07T07:04:17.1103392Z data = yaml.safe_load(yaml_text) 2025-09-07T07:04:17.1103905Z return data 2025-09-07T07:04:17.1104332Z except yaml.YAMLError: 2025-09-07T07:04:17.1104819Z log.exception("Error loading YAML") 2025-09-07T07:04:17.1105337Z raise 2025-09-07T07:04:17.1105559Z 2025-09-07T07:04:17.1105565Z 2025-09-07T07:04:17.1105977Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T07:04:17.1106717Z """ 2025-09-07T07:04:17.1107539Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T07:04:17.1108152Z 2025-09-07T07:04:17.1108653Z If the issue body contains "---" then the text above that is the settings 2025-09-07T07:04:17.1109422Z and the text below is the list of opted in users. 2025-09-07T07:04:17.1109822Z 2025-09-07T07:04:17.1110192Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T07:04:17.1110890Z """ 2025-09-07T07:04:17.1111340Z rollout_state_parts = rollout_state.split("---") 2025-09-07T07:04:17.1111964Z if len(rollout_state_parts) >= 2: 2025-09-07T07:04:17.1112564Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T07:04:17.1113143Z else: 2025-09-07T07:04:17.1113523Z return "", rollout_state 2025-09-07T07:04:17.1113829Z 2025-09-07T07:04:17.1113837Z 2025-09-07T07:04:17.1114038Z class UserOptins(dict[str, list[str]]): 2025-09-07T07:04:17.1114546Z """ 2025-09-07T07:04:17.1115058Z Dictionary of users with a list of features they have opted into 2025-09-07T07:04:17.1115706Z """ 2025-09-07T07:04:17.1115905Z 2025-09-07T07:04:17.1115917Z 2025-09-07T07:04:17.1116266Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T07:04:17.1116917Z """ 2025-09-07T07:04:17.1117821Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T07:04:17.1118512Z 2025-09-07T07:04:17.1119123Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T07:04:17.1120107Z - Example line: "@User1,lf,split_build" 2025-09-07T07:04:17.1120785Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T07:04:17.1121269Z 2025-09-07T07:04:17.1121276Z 2025-09-07T07:04:17.1121433Z """ 2025-09-07T07:04:17.1121815Z optins = UserOptins() 2025-09-07T07:04:17.1122308Z for user in user_optin_text.split("\n"): 2025-09-07T07:04:17.1122861Z user = user.strip("\r\n\t -") 2025-09-07T07:04:17.1123395Z if not user or not user.startswith("@"): 2025-09-07T07:04:17.1124107Z # Not a valid user. Skip 2025-09-07T07:04:17.1124589Z continue 2025-09-07T07:04:17.1124850Z 2025-09-07T07:04:17.1125013Z if user: 2025-09-07T07:04:17.1125449Z usr_name = user.split(",")[0].strip("@") 2025-09-07T07:04:17.1126132Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T07:04:17.1126618Z 2025-09-07T07:04:17.1126795Z return optins 2025-09-07T07:04:17.1127035Z 2025-09-07T07:04:17.1127042Z 2025-09-07T07:04:17.1127528Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T07:04:17.1128153Z """ 2025-09-07T07:04:17.1128554Z Check if the experiment name is valid. 2025-09-07T07:04:17.1129082Z A valid name: 2025-09-07T07:04:17.1129713Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T07:04:17.1130631Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T07:04:17.1131381Z - Cannot contain spaces 2025-09-07T07:04:17.1131857Z """ 2025-09-07T07:04:17.1132065Z 2025-09-07T07:04:17.1132325Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T07:04:17.1133021Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T07:04:17.1133465Z 2025-09-07T07:04:17.1133633Z if valid: 2025-09-07T07:04:17.1134020Z return True 2025-09-07T07:04:17.1134262Z 2025-09-07T07:04:17.1134427Z log.error( 2025-09-07T07:04:17.1135869Z 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-07T07:04:17.1137707Z ) 2025-09-07T07:04:17.1138080Z return False 2025-09-07T07:04:17.1138318Z 2025-09-07T07:04:17.1138325Z 2025-09-07T07:04:17.1138638Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T07:04:17.1139254Z """ 2025-09-07T07:04:17.1140002Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T07:04:17.1140752Z """ 2025-09-07T07:04:17.1141108Z try: 2025-09-07T07:04:17.1141479Z if settings_text: 2025-09-07T07:04:17.1142207Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T07:04:17.1142991Z # for easy reading 2025-09-07T07:04:17.1143761Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T07:04:17.1144635Z # the backtick character in shell commands. 2025-09-07T07:04:17.1145231Z backtick = chr(96) # backtick character 2025-09-07T07:04:17.1145889Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T07:04:17.1146539Z settings = load_yaml(settings_text) 2025-09-07T07:04:17.1146903Z 2025-09-07T07:04:17.1147497Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T07:04:17.1148272Z experiments = {} 2025-09-07T07:04:17.1148567Z 2025-09-07T07:04:17.1148944Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T07:04:17.1149713Z if not is_valid_experiment_name(exp_name): 2025-09-07T07:04:17.1150822Z # 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-07T07:04:17.1151857Z continue 2025-09-07T07:04:17.1152144Z 2025-09-07T07:04:17.1152328Z valid_settings = {} 2025-09-07T07:04:17.1152847Z for setting in exp_settings: 2025-09-07T07:04:17.1153419Z if setting not in Experiment._fields: 2025-09-07T07:04:17.1153968Z log.warning( 2025-09-07T07:04:17.1154664Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T07:04:17.1155531Z ) 2025-09-07T07:04:17.1155958Z else: 2025-09-07T07:04:17.1156468Z valid_settings[setting] = exp_settings[setting] 2025-09-07T07:04:17.1156890Z 2025-09-07T07:04:17.1157164Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T07:04:17.1158030Z return Settings(experiments) 2025-09-07T07:04:17.1158395Z 2025-09-07T07:04:17.1158573Z except Exception: 2025-09-07T07:04:17.1159057Z log.exception("Failed to parse settings") 2025-09-07T07:04:17.1159443Z 2025-09-07T07:04:17.1159615Z return Settings() 2025-09-07T07:04:17.1159878Z 2025-09-07T07:04:17.1159884Z 2025-09-07T07:04:17.1160129Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T07:04:17.1160690Z """ 2025-09-07T07:04:17.1161117Z Parse settings, if any, from the rollout state. 2025-09-07T07:04:17.1161525Z 2025-09-07T07:04:17.1161881Z If the issue body contains "---" then the text above that is the settings 2025-09-07T07:04:17.1162649Z and the text below is the list of opted in users. 2025-09-07T07:04:17.1163061Z 2025-09-07T07:04:17.1163465Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T07:04:17.1164220Z """ 2025-09-07T07:04:17.1164778Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:04:17.1165531Z return parse_settings_from_text(settings_text) 2025-09-07T07:04:17.1165931Z 2025-09-07T07:04:17.1165938Z 2025-09-07T07:04:17.1166181Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T07:04:17.1166751Z """ 2025-09-07T07:04:17.1167145Z Parse users from the rollout state. 2025-09-07T07:04:17.1167747Z 2025-09-07T07:04:17.1167912Z """ 2025-09-07T07:04:17.1168441Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:04:17.1169173Z return parse_user_opt_in_from_text(users_text) 2025-09-07T07:04:17.1169582Z 2025-09-07T07:04:17.1169596Z 2025-09-07T07:04:17.1170159Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:04:17.1170922Z """ 2025-09-07T07:04:17.1171350Z Check if a user is opted into an experiment 2025-09-07T07:04:17.1171898Z """ 2025-09-07T07:04:17.1172357Z return experiment_name in user_optins.get(user, []) 2025-09-07T07:04:17.1172785Z 2025-09-07T07:04:17.1172792Z 2025-09-07T07:04:17.1173204Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:04:17.1173949Z """ 2025-09-07T07:04:17.1174415Z Check if a user explicitly opted out of an experiment 2025-09-07T07:04:17.1174983Z """ 2025-09-07T07:04:17.1175497Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T07:04:17.1176178Z experiment_optout = "-" + experiment_name 2025-09-07T07:04:17.1176825Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T07:04:17.1177650Z return False 2025-09-07T07:04:17.1177937Z 2025-09-07T07:04:17.1178252Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T07:04:17.1178862Z log.warning( 2025-09-07T07:04:17.1179656Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T07:04:17.1180532Z ) 2025-09-07T07:04:17.1180745Z 2025-09-07T07:04:17.1180922Z return True 2025-09-07T07:04:17.1181168Z 2025-09-07T07:04:17.1181174Z 2025-09-07T07:04:17.1181356Z def get_runner_prefix( 2025-09-07T07:04:17.1181799Z rollout_state: str, 2025-09-07T07:04:17.1182262Z workflow_requestors: Iterable[str], 2025-09-07T07:04:17.1182783Z branch: str, 2025-09-07T07:04:17.1183275Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T07:04:17.1183938Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T07:04:17.1184518Z is_canary: bool = False, 2025-09-07T07:04:17.1184976Z ) -> str: 2025-09-07T07:04:17.1185397Z settings = parse_settings(rollout_state) 2025-09-07T07:04:17.1186149Z user_optins = parse_users(rollout_state) 2025-09-07T07:04:17.1186520Z 2025-09-07T07:04:17.1186700Z fleet_prefix = "" 2025-09-07T07:04:17.1187126Z prefixes = [] 2025-09-07T07:04:17.1187955Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T07:04:17.1188886Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T07:04:17.1189605Z log.info( 2025-09-07T07:04:17.1190279Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T07:04:17.1191034Z ) 2025-09-07T07:04:17.1191419Z continue 2025-09-07T07:04:17.1191677Z 2025-09-07T07:04:17.1191865Z if opt_out_experiments: 2025-09-07T07:04:17.1192394Z if experiment_name in opt_out_experiments: 2025-09-07T07:04:17.1193047Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T07:04:17.1193642Z log.info( 2025-09-07T07:04:17.1194550Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T07:04:17.1195522Z ) 2025-09-07T07:04:17.1195923Z continue 2025-09-07T07:04:17.1196195Z 2025-09-07T07:04:17.1196387Z if eligible_experiments: 2025-09-07T07:04:17.1196951Z if experiment_name not in eligible_experiments: 2025-09-07T07:04:17.1197764Z exp_list = ", ".join(eligible_experiments) 2025-09-07T07:04:17.1198328Z log.info( 2025-09-07T07:04:17.1199098Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T07:04:17.1199931Z ) 2025-09-07T07:04:17.1200331Z continue 2025-09-07T07:04:17.1200815Z elif not experiment_settings.default: 2025-09-07T07:04:17.1201346Z log.info( 2025-09-07T07:04:17.1202143Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T07:04:17.1202886Z ) 2025-09-07T07:04:17.1203277Z continue 2025-09-07T07:04:17.1203285Z 2025-09-07T07:04:17.1203558Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T07:04:17.1203751Z opted_out_users = [ 2025-09-07T07:04:17.1203918Z requestor 2025-09-07T07:04:17.1204129Z for requestor in workflow_requestors 2025-09-07T07:04:17.1204433Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T07:04:17.1204599Z ] 2025-09-07T07:04:17.1204607Z 2025-09-07T07:04:17.1204790Z if opted_out_users: 2025-09-07T07:04:17.1204961Z log.info( 2025-09-07T07:04:17.1205335Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T07:04:17.1205499Z ) 2025-09-07T07:04:17.1205664Z continue 2025-09-07T07:04:17.1205676Z 2025-09-07T07:04:17.1205960Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T07:04:17.1206137Z opted_in_users = [ 2025-09-07T07:04:17.1206308Z requestor 2025-09-07T07:04:17.1206523Z for requestor in workflow_requestors 2025-09-07T07:04:17.1206810Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T07:04:17.1206972Z ] 2025-09-07T07:04:17.1206980Z 2025-09-07T07:04:17.1207153Z enabled = False 2025-09-07T07:04:17.1207539Z if opted_in_users: 2025-09-07T07:04:17.1207726Z log.info( 2025-09-07T07:04:17.1208080Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T07:04:17.1208245Z ) 2025-09-07T07:04:17.1208423Z enabled = True 2025-09-07T07:04:17.1208431Z 2025-09-07T07:04:17.1208643Z elif experiment_settings.rollout_perc: 2025-09-07T07:04:17.1209104Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T07:04:17.1209557Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T07:04:17.1209731Z log.info( 2025-09-07T07:04:17.1210316Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T07:04:17.1210480Z ) 2025-09-07T07:04:17.1210659Z enabled = True 2025-09-07T07:04:17.1210667Z 2025-09-07T07:04:17.1210831Z if enabled: 2025-09-07T07:04:17.1211019Z label = experiment_name 2025-09-07T07:04:17.1211239Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T07:04:17.1211666Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T07:04:17.1211934Z # - If it's enabled, then we always list it's prefix first 2025-09-07T07:04:17.1212244Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T07:04:17.1212428Z if is_canary: 2025-09-07T07:04:17.1212627Z label += CANARY_FLEET_SUFFIX 2025-09-07T07:04:17.1212814Z fleet_prefix = label 2025-09-07T07:04:17.1212980Z else: 2025-09-07T07:04:17.1213197Z prefixes.append(label) 2025-09-07T07:04:17.1213206Z 2025-09-07T07:04:17.1213395Z if len(prefixes) > 1: 2025-09-07T07:04:17.1213564Z log.error( 2025-09-07T07:04:17.1214377Z 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-07T07:04:17.1214545Z ) 2025-09-07T07:04:17.1214740Z prefixes = prefixes[:1] 2025-09-07T07:04:17.1214747Z 2025-09-07T07:04:17.1214936Z # Fleet always comes first 2025-09-07T07:04:17.1215104Z if fleet_prefix: 2025-09-07T07:04:17.1215314Z prefixes.insert(0, fleet_prefix) 2025-09-07T07:04:17.1215327Z 2025-09-07T07:04:17.1215700Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T07:04:17.1215710Z 2025-09-07T07:04:17.1215718Z 2025-09-07T07:04:17.1216172Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T07:04:17.1216344Z """ 2025-09-07T07:04:17.1216733Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T07:04:17.1216741Z 2025-09-07T07:04:17.1217121Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T07:04:17.1217500Z """ 2025-09-07T07:04:17.1217714Z gh = get_gh_client(github_token) 2025-09-07T07:04:17.1217922Z issue = get_issue(gh, repo, issue_num) 2025-09-07T07:04:17.1218210Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T07:04:17.1218219Z 2025-09-07T07:04:17.1218225Z 2025-09-07T07:04:17.1218620Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T07:04:17.1218817Z for _ in range(num_retries): 2025-09-07T07:04:17.1218988Z try: 2025-09-07T07:04:17.1219201Z req = Request(url=url, headers=headers) 2025-09-07T07:04:17.1219486Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T07:04:17.1219682Z return json.loads(content) 2025-09-07T07:04:17.1219879Z except Exception as e: 2025-09-07T07:04:17.1220116Z log.warning(f"Could not download {url}: {e}") 2025-09-07T07:04:17.1220124Z 2025-09-07T07:04:17.1220498Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T07:04:17.1220668Z return {} 2025-09-07T07:04:17.1220676Z 2025-09-07T07:04:17.1220682Z 2025-09-07T07:04:17.1220846Z @cache 2025-09-07T07:04:17.1221272Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T07:04:17.1221438Z """ 2025-09-07T07:04:17.1221636Z Dynamically get PR information 2025-09-07T07:04:17.1221797Z """ 2025-09-07T07:04:17.1222237Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T07:04:17.1222413Z headers = { 2025-09-07T07:04:17.1222645Z "Accept": "application/vnd.github.v3+json", 2025-09-07T07:04:17.1222863Z "Authorization": f"token {github_token}", 2025-09-07T07:04:17.1223031Z } 2025-09-07T07:04:17.1223270Z json_response: dict[str, Any] = download_json( 2025-09-07T07:04:17.1223483Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T07:04:17.1223658Z headers=headers, 2025-09-07T07:04:17.1223829Z ) 2025-09-07T07:04:17.1223837Z 2025-09-07T07:04:17.1224022Z if not json_response: 2025-09-07T07:04:17.1224309Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T07:04:17.1224479Z return {} 2025-09-07T07:04:17.1224488Z 2025-09-07T07:04:17.1224665Z return json_response 2025-09-07T07:04:17.1224673Z 2025-09-07T07:04:17.1224679Z 2025-09-07T07:04:17.1225074Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T07:04:17.1225253Z """ 2025-09-07T07:04:17.1225580Z Dynamically get the latest list of labels from the pull request 2025-09-07T07:04:17.1225744Z """ 2025-09-07T07:04:17.1226041Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T07:04:17.1226208Z return { 2025-09-07T07:04:17.1226572Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T07:04:17.1226734Z } 2025-09-07T07:04:17.1226742Z 2025-09-07T07:04:17.1226755Z 2025-09-07T07:04:17.1226933Z def main() -> None: 2025-09-07T07:04:17.1227115Z args = parse_args() 2025-09-07T07:04:17.1227123Z 2025-09-07T07:04:17.1227526Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T07:04:17.1227537Z 2025-09-07T07:04:17.1227749Z # Check if the PR is opt-out 2025-09-07T07:04:17.1227922Z if args.pr_number: 2025-09-07T07:04:17.1228314Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T07:04:17.1228648Z if OPT_OUT_LABEL in labels: 2025-09-07T07:04:17.1228833Z log.info( 2025-09-07T07:04:17.1229269Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T07:04:17.1229447Z ) 2025-09-07T07:04:17.1229779Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:04:17.1229949Z sys.exit() 2025-09-07T07:04:17.1229955Z 2025-09-07T07:04:17.1230120Z try: 2025-09-07T07:04:17.1230364Z rollout_state = get_rollout_state_from_issue( 2025-09-07T07:04:17.1230696Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T07:04:17.1230875Z ) 2025-09-07T07:04:17.1230883Z 2025-09-07T07:04:17.1231091Z username = get_potential_pr_author( 2025-09-07T07:04:17.1231274Z args.github_token, 2025-09-07T07:04:17.1231456Z args.github_repo, 2025-09-07T07:04:17.1231631Z args.github_actor, 2025-09-07T07:04:17.1231825Z args.github_ref_type, 2025-09-07T07:04:17.1232019Z args.github_branch, 2025-09-07T07:04:17.1232182Z ) 2025-09-07T07:04:17.1232189Z 2025-09-07T07:04:17.1232484Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T07:04:17.1232494Z 2025-09-07T07:04:17.1232711Z runner_label_prefix = get_runner_prefix( 2025-09-07T07:04:17.1232891Z rollout_state, 2025-09-07T07:04:17.1233106Z (args.github_issue_owner, username), 2025-09-07T07:04:17.1233288Z args.github_branch, 2025-09-07T07:04:17.1233481Z args.eligible_experiments, 2025-09-07T07:04:17.1233680Z args.opt_out_experiments, 2025-09-07T07:04:17.1233856Z is_canary, 2025-09-07T07:04:17.1234019Z ) 2025-09-07T07:04:17.1234026Z 2025-09-07T07:04:17.1234214Z except Exception as e: 2025-09-07T07:04:17.1234388Z log.error( 2025-09-07T07:04:17.1234823Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T07:04:17.1235115Z ) 2025-09-07T07:04:17.1235124Z 2025-09-07T07:04:17.1235461Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:04:17.1235469Z 2025-09-07T07:04:17.1235477Z 2025-09-07T07:04:17.1235657Z if __name__ == "__main__": 2025-09-07T07:04:17.1235819Z main() 2025-09-07T07:04:17.1235827Z 2025-09-07T07:04:17.1331147Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T07:04:17.1332111Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T07:04:17.1363764Z shell: /usr/bin/bash -e {0} 2025-09-07T07:04:17.1364259Z env: 2025-09-07T07:04:17.1364905Z GITHUB_TOKEN: *** 2025-09-07T07:04:17.1365327Z ISSUE_NUMBER: 5132 2025-09-07T07:04:17.1365779Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:04:17.1366280Z ISSUE_OWNER: 2025-09-07T07:04:17.1366691Z CHECK_EXPERIMENTS: 2025-09-07T07:04:17.1367138Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:04:17.1367736Z PR_NUMBER: 2025-09-07T07:04:17.1368150Z ##[endgroup] 2025-09-07T07:04:18.2792410Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T07:04:19.5368226Z Collecting urllib3==1.26.18 2025-09-07T07:04:19.5815344Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T07:04:19.6050789Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.8 MB/s eta 0:00:00 2025-09-07T07:04:19.6316359Z Collecting PyGithub==2.3.0 2025-09-07T07:04:19.6386096Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T07:04:19.6845112Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T07:04:19.6913336Z 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-07T07:04:19.6959348Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-09-07T07:04:19.6970926Z 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-07T07:04:19.6989791Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-09-07T07:04:19.7268593Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T07:04:19.7339149Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T07:04:19.7564727Z 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-07T07:04:19.8837234Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T07:04:19.8909957Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T07:04:20.0139625Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T07:04:20.0211781Z 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-07T07:04:20.0438465Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T07:04:20.0506847Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T07:04:20.0773944Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T07:04:20.0919409Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 10.6 MB/s eta 0:00:00 2025-09-07T07:04:20.1009961Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T07:04:20.1318094Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 11.9 MB/s eta 0:00:00 2025-09-07T07:04:20.1392206Z 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-07T07:04:20.1842609Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 19.5 MB/s eta 0:00:00 2025-09-07T07:04:20.1913459Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T07:04:20.2012715Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T07:04:20.2224286Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 24.0 MB/s eta 0:00:00 2025-09-07T07:04:20.2291993Z 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-07T07:04:20.2341385Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 23.1 MB/s eta 0:00:00 2025-09-07T07:04:20.2409248Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T07:04:20.2463902Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 27.6 MB/s eta 0:00:00 2025-09-07T07:04:20.5576871Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T07:04:21.0847021Z 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-07T07:04:21.1629963Z ##[group]Run curr_branch="main" 2025-09-07T07:04:21.1630276Z curr_branch="main" 2025-09-07T07:04:21.1630493Z curr_ref_type="branch" 2025-09-07T07:04:21.1630771Z echo "Current branch is '$curr_branch'" 2025-09-07T07:04:21.1631017Z  2025-09-07T07:04:21.1631206Z python3 runner_determinator.py \ 2025-09-07T07:04:21.1631482Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T07:04:21.1631746Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T07:04:21.1632001Z  --github-branch "$curr_branch" \ 2025-09-07T07:04:21.1632268Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T07:04:21.1632542Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T07:04:21.1632808Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T07:04:21.1633071Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T07:04:21.1633359Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T07:04:21.1633721Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T07:04:21.1634005Z  --pr-number "${PR_NUMBER}" 2025-09-07T07:04:21.1665329Z shell: /usr/bin/bash -e {0} 2025-09-07T07:04:21.1665551Z env: 2025-09-07T07:04:21.1666109Z GITHUB_TOKEN: *** 2025-09-07T07:04:21.1666299Z ISSUE_NUMBER: 5132 2025-09-07T07:04:21.1666498Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:04:21.1666739Z ISSUE_OWNER: 2025-09-07T07:04:21.1666914Z CHECK_EXPERIMENTS: 2025-09-07T07:04:21.1667111Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:04:21.1667523Z PR_NUMBER: 2025-09-07T07:04:21.1667690Z ##[endgroup] 2025-09-07T07:04:21.1715759Z Current branch is 'main' 2025-09-07T07:04:22.5741129Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-09-07T07:04:22.5742410Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T07:04:22.5743098Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T07:04:22.5743764Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T07:04:22.5744327Z INFO : Setting output: label-type='' 2025-09-07T07:04:22.6061280Z Evaluate and set job outputs 2025-09-07T07:04:22.6068040Z Cleaning up orphan processes