2025-09-07T07:06:52.9999700Z Current runner version: '2.328.0' 2025-09-07T07:06:53.0033177Z ##[group]Runner Image Provisioner 2025-09-07T07:06:53.0034453Z Hosted Compute Agent 2025-09-07T07:06:53.0035336Z Version: 20250829.383 2025-09-07T07:06:53.0036301Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T07:06:53.0037495Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T07:06:53.0038665Z ##[endgroup] 2025-09-07T07:06:53.0039706Z ##[group]Operating System 2025-09-07T07:06:53.0040650Z Ubuntu 2025-09-07T07:06:53.0041499Z 24.04.3 2025-09-07T07:06:53.0042357Z LTS 2025-09-07T07:06:53.0043162Z ##[endgroup] 2025-09-07T07:06:53.0043933Z ##[group]Runner Image 2025-09-07T07:06:53.0045003Z Image: ubuntu-24.04 2025-09-07T07:06:53.0045850Z Version: 20250831.1.0 2025-09-07T07:06:53.0047501Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T07:06:53.0050538Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T07:06:53.0052395Z ##[endgroup] 2025-09-07T07:06:53.0056489Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T07:06:53.0060184Z Actions: read 2025-09-07T07:06:53.0061056Z Attestations: read 2025-09-07T07:06:53.0061914Z Checks: read 2025-09-07T07:06:53.0062834Z Contents: read 2025-09-07T07:06:53.0063620Z Deployments: read 2025-09-07T07:06:53.0064474Z Discussions: read 2025-09-07T07:06:53.0065940Z Issues: read 2025-09-07T07:06:53.0066787Z Metadata: read 2025-09-07T07:06:53.0067588Z Models: read 2025-09-07T07:06:53.0068792Z Packages: read 2025-09-07T07:06:53.0069636Z Pages: read 2025-09-07T07:06:53.0070536Z PullRequests: read 2025-09-07T07:06:53.0071568Z RepositoryProjects: read 2025-09-07T07:06:53.0072532Z SecurityEvents: read 2025-09-07T07:06:53.0073611Z Statuses: read 2025-09-07T07:06:53.0074408Z ##[endgroup] 2025-09-07T07:06:53.0077634Z Secret source: Actions 2025-09-07T07:06:53.0079197Z Prepare workflow directory 2025-09-07T07:06:53.0829767Z Prepare all required actions 2025-09-07T07:06:53.0912316Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T07:06:53.0920030Z ##[group] Inputs 2025-09-07T07:06:53.0921024Z check_experiments: 2025-09-07T07:06:53.0922037Z opt_out_experiments: lf 2025-09-07T07:06:53.0923179Z triggering_actor: pytorchmergebot 2025-09-07T07:06:53.0924165Z issue_owner: 2025-09-07T07:06:53.0925093Z curr_branch: main 2025-09-07T07:06:53.0925981Z curr_ref_type: branch 2025-09-07T07:06:53.0926808Z issue_number: 5132 2025-09-07T07:06:53.0927787Z ##[endgroup] 2025-09-07T07:06:53.0929062Z Complete job name: get-label-type / runner-determinator 2025-09-07T07:06:53.1723635Z ##[group]Run cat < runner_determinator.py 2025-09-07T07:06:53.1725969Z cat < runner_determinator.py 2025-09-07T07:06:53.1726705Z # flake8: noqa: G004 2025-09-07T07:06:53.1727208Z  2025-09-07T07:06:53.1728121Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T07:06:53.1729207Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T07:06:53.1730177Z # python .github/scripts/update_runner_determinator.py 2025-09-07T07:06:53.1730867Z  2025-09-07T07:06:53.1731296Z """ 2025-09-07T07:06:53.1732002Z This runner determinator is used to determine which set of runners to run a 2025-09-07T07:06:53.1733017Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T07:06:53.1734204Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T07:06:53.1735105Z of which runners should be used to run which job. 2025-09-07T07:06:53.1735731Z  2025-09-07T07:06:53.1736432Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T07:06:53.1737440Z separated by a line containing "---". If the line is not present, the 2025-09-07T07:06:53.1738926Z settings are considered to be empty with only the second part, the user 2025-09-07T07:06:53.1739841Z list, defined. 2025-09-07T07:06:53.1740340Z  2025-09-07T07:06:53.1740954Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T07:06:53.1742029Z used to define any settings that are needed to determine which runners to use. 2025-09-07T07:06:53.1742964Z It's fields are defined by the RolloutSettings class below. 2025-09-07T07:06:53.1743620Z  2025-09-07T07:06:53.1744375Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T07:06:53.1745314Z The user list is also a comma separated list of additional features or 2025-09-07T07:06:53.1746163Z experiments which the user could be opted in to. 2025-09-07T07:06:53.1746852Z  2025-09-07T07:06:53.1747299Z The user list has the following rules: 2025-09-07T07:06:53.1748008Z  2025-09-07T07:06:53.1748750Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T07:06:53.1749718Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T07:06:53.1750541Z - A "#" prefix opts the user out of all experiments 2025-09-07T07:06:53.1751212Z  2025-09-07T07:06:53.1751671Z Example config: 2025-09-07T07:06:53.1752224Z  # A list of experiments that can be opted into. 2025-09-07T07:06:53.1753056Z  # This defines the behavior they'll induce when opted into. 2025-09-07T07:06:53.1753758Z  # Expected syntax is: 2025-09-07T07:06:53.1754526Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T07:06:53.1755610Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T07:06:53.1756422Z  2025-09-07T07:06:53.1756868Z  experiments: 2025-09-07T07:06:53.1757345Z  lf: 2025-09-07T07:06:53.1758016Z  rollout_percent: 25 2025-09-07T07:06:53.1758591Z  all_branches: false 2025-09-07T07:06:53.1759168Z  default: true 2025-09-07T07:06:53.1759780Z  --- 2025-09-07T07:06:53.1760211Z  2025-09-07T07:06:53.1760679Z  # Opt-ins: 2025-09-07T07:06:53.1761518Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T07:06:53.1762623Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T07:06:53.1763518Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T07:06:53.1764344Z  # Experiments should be from the above list. 2025-09-07T07:06:53.1765019Z  2025-09-07T07:06:53.1765457Z  @User1,-lf,split_build 2025-09-07T07:06:53.1766079Z  @User2,lf 2025-09-07T07:06:53.1766600Z  @User3,split_build 2025-09-07T07:06:53.1767097Z """ 2025-09-07T07:06:53.1767737Z  2025-09-07T07:06:53.1768306Z import json 2025-09-07T07:06:53.1768809Z import logging 2025-09-07T07:06:53.1769313Z import os 2025-09-07T07:06:53.1769816Z import random 2025-09-07T07:06:53.1770280Z import re 2025-09-07T07:06:53.1770790Z import sys 2025-09-07T07:06:53.1771368Z from argparse import ArgumentParser 2025-09-07T07:06:53.1771987Z from collections.abc import Iterable 2025-09-07T07:06:53.1772648Z from functools import cache 2025-09-07T07:06:53.1773219Z from logging import LogRecord 2025-09-07T07:06:53.1773832Z from typing import Any, NamedTuple 2025-09-07T07:06:53.1774448Z from urllib.request import Request, urlopen 2025-09-07T07:06:53.1775174Z  2025-09-07T07:06:53.1775614Z import yaml 2025-09-07T07:06:53.1776244Z from github import Auth, Github 2025-09-07T07:06:53.1776925Z from github.Issue import Issue 2025-09-07T07:06:53.1777467Z  2025-09-07T07:06:53.1778051Z  2025-09-07T07:06:53.1778802Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T07:06:53.1779629Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T07:06:53.1780588Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T07:06:53.1781418Z  2025-09-07T07:06:53.1781964Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T07:06:53.1782615Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T07:06:53.1783337Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T07:06:53.1783977Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T07:06:53.1784587Z  2025-09-07T07:06:53.1785116Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T07:06:53.1785678Z  2025-09-07T07:06:53.1786138Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T07:06:53.1786751Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T07:06:53.1787320Z  2025-09-07T07:06:53.1787705Z  2025-09-07T07:06:53.1788553Z class Experiment(NamedTuple): 2025-09-07T07:06:53.1789195Z  rollout_perc: float = ( 2025-09-07T07:06:53.1789939Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T07:06:53.1850474Z  ) 2025-09-07T07:06:53.1851256Z  all_branches: bool = ( 2025-09-07T07:06:53.1852497Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T07:06:53.1853707Z  ) 2025-09-07T07:06:53.1854428Z  default: bool = ( 2025-09-07T07:06:53.1855329Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T07:06:53.1855988Z  ) 2025-09-07T07:06:53.1856360Z  2025-09-07T07:06:53.1856732Z  # Add more fields as needed 2025-09-07T07:06:53.1857216Z  2025-09-07T07:06:53.1857549Z  2025-09-07T07:06:53.1858157Z class Settings(NamedTuple): 2025-09-07T07:06:53.1858692Z  """ 2025-09-07T07:06:53.1859193Z  Settings for the experiments that can be opted into. 2025-09-07T07:06:53.1859778Z  """ 2025-09-07T07:06:53.1860139Z  2025-09-07T07:06:53.1860541Z  experiments: dict[str, Experiment] = {} 2025-09-07T07:06:53.1861045Z  2025-09-07T07:06:53.1861577Z  2025-09-07T07:06:53.1862009Z class ColorFormatter(logging.Formatter): 2025-09-07T07:06:53.1862660Z  """Color codes the log messages based on the log level""" 2025-09-07T07:06:53.1863243Z  2025-09-07T07:06:53.1863583Z  COLORS = { 2025-09-07T07:06:53.1864015Z  "WARNING": "\033[33m", # Yellow 2025-09-07T07:06:53.1864542Z  "ERROR": "\033[31m", # Red 2025-09-07T07:06:53.1865038Z  "CRITICAL": "\033[31m", # Red 2025-09-07T07:06:53.1865553Z  "INFO": "\033[0m", # Reset 2025-09-07T07:06:53.1866061Z  "DEBUG": "\033[0m", # Reset 2025-09-07T07:06:53.1866545Z  } 2025-09-07T07:06:53.1866892Z  2025-09-07T07:06:53.1867312Z  def format(self, record: LogRecord) -> str: 2025-09-07T07:06:53.1868423Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T07:06:53.1869237Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T07:06:53.1869832Z  return super().format(record) 2025-09-07T07:06:53.1870311Z  2025-09-07T07:06:53.1870644Z  2025-09-07T07:06:53.1871036Z handler = logging.StreamHandler() 2025-09-07T07:06:53.1871779Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T07:06:53.1872636Z  2025-09-07T07:06:53.1873095Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T07:06:53.1873692Z log.addHandler(handler) 2025-09-07T07:06:53.1874166Z log.setLevel(logging.INFO) 2025-09-07T07:06:53.1874623Z  2025-09-07T07:06:53.1874949Z  2025-09-07T07:06:53.1875403Z def set_github_output(key: str, value: str) -> None: 2025-09-07T07:06:53.1875964Z  """ 2025-09-07T07:06:53.1876492Z  Defines outputs of the github action that invokes this script 2025-09-07T07:06:53.1877114Z  """ 2025-09-07T07:06:53.1877499Z  if not GITHUB_OUTPUT: 2025-09-07T07:06:53.1878764Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T07:06:53.1879846Z  log.warning( 2025-09-07T07:06:53.1880703Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T07:06:53.1881602Z  ) 2025-09-07T07:06:53.1882062Z  print(f"::set-output name={key}::{value}") 2025-09-07T07:06:53.1882596Z  return 2025-09-07T07:06:53.1882985Z  2025-09-07T07:06:53.1883373Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T07:06:53.1883946Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T07:06:53.1884512Z  f.write(f"{key}={value}\n") 2025-09-07T07:06:53.1884985Z  2025-09-07T07:06:53.1885323Z  2025-09-07T07:06:53.1885820Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T07:06:53.1886465Z  return frozenset( 2025-09-07T07:06:53.1887104Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T07:06:53.1887773Z  ) 2025-09-07T07:06:53.1888309Z  2025-09-07T07:06:53.1888640Z  2025-09-07T07:06:53.1889000Z def parse_args() -> Any: 2025-09-07T07:06:53.1889594Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T07:06:53.1890461Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T07:06:53.1891206Z  parser.add_argument( 2025-09-07T07:06:53.1891690Z  "--github-issue-repo", 2025-09-07T07:06:53.1892170Z  type=str, 2025-09-07T07:06:53.1892592Z  required=False, 2025-09-07T07:06:53.1893217Z  default="pytorch/test-infra", 2025-09-07T07:06:53.1893793Z  help="GitHub repo to get the issue", 2025-09-07T07:06:53.1894298Z  ) 2025-09-07T07:06:53.1894667Z  parser.add_argument( 2025-09-07T07:06:53.1895136Z  "--github-repo", 2025-09-07T07:06:53.1895625Z  type=str, 2025-09-07T07:06:53.1896062Z  required=True, 2025-09-07T07:06:53.1896560Z  help="GitHub repo where CI is running", 2025-09-07T07:06:53.1897095Z  ) 2025-09-07T07:06:53.1897470Z  parser.add_argument( 2025-09-07T07:06:53.1898325Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T07:06:53.1898977Z  ) 2025-09-07T07:06:53.1899357Z  parser.add_argument( 2025-09-07T07:06:53.1900009Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T07:06:53.1900673Z  ) 2025-09-07T07:06:53.1901052Z  parser.add_argument( 2025-09-07T07:06:53.1901717Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T07:06:53.1902390Z  ) 2025-09-07T07:06:53.1902759Z  parser.add_argument( 2025-09-07T07:06:53.1903439Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T07:06:53.1904265Z  ) 2025-09-07T07:06:53.1904643Z  parser.add_argument( 2025-09-07T07:06:53.1905105Z  "--github-ref-type", 2025-09-07T07:06:53.1905573Z  type=str, 2025-09-07T07:06:53.1906023Z  required=True, 2025-09-07T07:06:53.1906596Z  help="Current GitHub ref type, branch or tag", 2025-09-07T07:06:53.1907185Z  ) 2025-09-07T07:06:53.1907620Z  parser.add_argument( 2025-09-07T07:06:53.1908849Z  "--eligible-experiments", 2025-09-07T07:06:53.1909418Z  type=_str_comma_separated_to_set, 2025-09-07T07:06:53.1909958Z  required=False, 2025-09-07T07:06:53.1910412Z  default="", 2025-09-07T07:06:53.1911273Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T07:06:53.1912174Z  ) 2025-09-07T07:06:53.1912555Z  parser.add_argument( 2025-09-07T07:06:53.1913059Z  "--opt-out-experiments", 2025-09-07T07:06:53.1913594Z  type=_str_comma_separated_to_set, 2025-09-07T07:06:53.1914113Z  required=False, 2025-09-07T07:06:53.1914563Z  default="", 2025-09-07T07:06:53.1914981Z  help=( 2025-09-07T07:06:53.1915674Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T07:06:53.1916781Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T07:06:53.1917596Z  ), 2025-09-07T07:06:53.1918087Z  ) 2025-09-07T07:06:53.1918475Z  parser.add_argument( 2025-09-07T07:06:53.1918942Z  "--pr-number", 2025-09-07T07:06:53.1919385Z  type=str, 2025-09-07T07:06:53.1919811Z  required=False, 2025-09-07T07:06:53.1920249Z  default="", 2025-09-07T07:06:53.1920761Z  help="the optional PR number where this is run", 2025-09-07T07:06:53.1921301Z  ) 2025-09-07T07:06:53.1921650Z  2025-09-07T07:06:53.1922026Z  return parser.parse_args() 2025-09-07T07:06:53.1922497Z  2025-09-07T07:06:53.1922826Z  2025-09-07T07:06:53.1923417Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T07:06:53.1924315Z  auth = Auth.Token(github_token) 2025-09-07T07:06:53.1924852Z  return Github(auth=auth) 2025-09-07T07:06:53.1925318Z  2025-09-07T07:06:53.1925650Z  2025-09-07T07:06:53.1926293Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T07:06:53.1927087Z  repo = gh.get_repo(repo) 2025-09-07T07:06:53.1927621Z  return repo.get_issue(number=issue_num) 2025-09-07T07:06:53.1928245Z  2025-09-07T07:06:53.1928575Z  2025-09-07T07:06:53.1928938Z def get_potential_pr_author( 2025-09-07T07:06:53.1929611Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T07:06:53.1930267Z ) -> str: 2025-09-07T07:06:53.1930812Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T07:06:53.1931605Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T07:06:53.1932355Z  # embedded in the tag name: ciflow// 2025-09-07T07:06:53.1932912Z  2025-09-07T07:06:53.1933293Z  gh = get_gh_client(github_token) 2025-09-07T07:06:53.1933773Z  2025-09-07T07:06:53.1934232Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T07:06:53.1934857Z  split_tag = ref_name.split("/") 2025-09-07T07:06:53.1935475Z  if ( 2025-09-07T07:06:53.1935884Z  len(split_tag) == 3 2025-09-07T07:06:53.1936388Z  and split_tag[0] == "ciflow" 2025-09-07T07:06:53.1936928Z  and split_tag[2].isnumeric() 2025-09-07T07:06:53.1937429Z  ): 2025-09-07T07:06:53.1937845Z  pr_number = split_tag[2] 2025-09-07T07:06:53.1938599Z  try: 2025-09-07T07:06:53.1939071Z  repository = gh.get_repo(repo) 2025-09-07T07:06:53.1939697Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T07:06:53.1940301Z  except Exception as e: 2025-09-07T07:06:53.1940830Z  raise Exception( # noqa: TRY002 2025-09-07T07:06:53.1941493Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T07:06:53.1942122Z  ) from e 2025-09-07T07:06:53.1942691Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T07:06:53.1943390Z  # In all other cases, return the original input username 2025-09-07T07:06:53.1943974Z  return username 2025-09-07T07:06:53.1944383Z  2025-09-07T07:06:53.1944713Z  2025-09-07T07:06:53.1945124Z def is_exception_branch(branch: str) -> bool: 2025-09-07T07:06:53.1945648Z  """ 2025-09-07T07:06:53.1946303Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T07:06:53.1947050Z  """ 2025-09-07T07:06:53.1947607Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T07:06:53.1948758Z  2025-09-07T07:06:53.1949097Z  2025-09-07T07:06:53.1949472Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T07:06:53.1949963Z  try: 2025-09-07T07:06:53.1950368Z  data = yaml.safe_load(yaml_text) 2025-09-07T07:06:53.1950872Z  return data 2025-09-07T07:06:53.1951314Z  except yaml.YAMLError: 2025-09-07T07:06:53.1951826Z  log.exception("Error loading YAML") 2025-09-07T07:06:53.1952330Z  raise 2025-09-07T07:06:53.1952708Z  2025-09-07T07:06:53.1953035Z  2025-09-07T07:06:53.1953624Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T07:06:53.1954341Z  """ 2025-09-07T07:06:53.1955111Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T07:06:53.1955842Z  2025-09-07T07:06:53.1956367Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T07:06:53.1957110Z  and the text below is the list of opted in users. 2025-09-07T07:06:53.1957656Z  2025-09-07T07:06:53.1958332Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T07:06:53.1959002Z  """ 2025-09-07T07:06:53.1959455Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T07:06:53.1960037Z  if len(rollout_state_parts) >= 2: 2025-09-07T07:06:53.1960654Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T07:06:53.1961229Z  else: 2025-09-07T07:06:53.1961630Z  return "", rollout_state 2025-09-07T07:06:53.1962092Z  2025-09-07T07:06:53.1962421Z  2025-09-07T07:06:53.1962815Z class UserOptins(dict[str, list[str]]): 2025-09-07T07:06:53.1963307Z  """ 2025-09-07T07:06:53.1963838Z  Dictionary of users with a list of features they have opted into 2025-09-07T07:06:53.1964472Z  """ 2025-09-07T07:06:53.1964821Z  2025-09-07T07:06:53.1965137Z  2025-09-07T07:06:53.1965773Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T07:06:53.1966407Z  """ 2025-09-07T07:06:53.1967122Z  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:06:53.1968174Z  2025-09-07T07:06:53.1969109Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T07:06:53.1970084Z  - Example line: "@User1,lf,split_build" 2025-09-07T07:06:53.1970764Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T07:06:53.1971373Z  2025-09-07T07:06:53.1971689Z  2025-09-07T07:06:53.1972014Z  """ 2025-09-07T07:06:53.1972388Z  optins = UserOptins() 2025-09-07T07:06:53.1972892Z  for user in user_optin_text.split("\n"): 2025-09-07T07:06:53.1973439Z  user = user.strip("\r\n\t -") 2025-09-07T07:06:53.1973989Z  if not user or not user.startswith("@"): 2025-09-07T07:06:53.1974540Z  # Not a valid user. Skip 2025-09-07T07:06:53.1975024Z  continue 2025-09-07T07:06:53.1975430Z  2025-09-07T07:06:53.1975763Z  if user: 2025-09-07T07:06:53.1976224Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T07:06:53.1976900Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T07:06:53.1977515Z  2025-09-07T07:06:53.1977854Z  return optins 2025-09-07T07:06:53.1978502Z  2025-09-07T07:06:53.1978835Z  2025-09-07T07:06:53.1979327Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T07:06:53.1979934Z  """ 2025-09-07T07:06:53.1980349Z  Check if the experiment name is valid. 2025-09-07T07:06:53.1980855Z  A valid name: 2025-09-07T07:06:53.1981515Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T07:06:53.1982423Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T07:06:53.1983107Z  - Cannot contain spaces 2025-09-07T07:06:53.1983568Z  """ 2025-09-07T07:06:53.1983915Z  2025-09-07T07:06:53.1984357Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T07:06:53.1985048Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T07:06:53.1985753Z  2025-09-07T07:06:53.1986087Z  if valid: 2025-09-07T07:06:53.1986483Z  return True 2025-09-07T07:06:53.1986884Z  2025-09-07T07:06:53.1987218Z  log.error( 2025-09-07T07:06:53.1988844Z  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:06:53.1990320Z  ) 2025-09-07T07:06:53.1990679Z  return False 2025-09-07T07:06:53.1991069Z  2025-09-07T07:06:53.1991388Z  2025-09-07T07:06:53.1991884Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T07:06:53.1992506Z  """ 2025-09-07T07:06:53.1993106Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T07:06:53.1993790Z  """ 2025-09-07T07:06:53.1994148Z  try: 2025-09-07T07:06:53.1994515Z  if settings_text: 2025-09-07T07:06:53.1995246Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T07:06:53.1996028Z  # for easy reading 2025-09-07T07:06:53.1996885Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T07:06:53.1998199Z  # the backtick character in shell commands. 2025-09-07T07:06:53.1998815Z  backtick = chr(96) # backtick character 2025-09-07T07:06:53.1999476Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T07:06:53.2000136Z  settings = load_yaml(settings_text) 2025-09-07T07:06:53.2000628Z  2025-09-07T07:06:53.2001204Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T07:06:53.2001918Z  experiments = {} 2025-09-07T07:06:53.2002361Z  2025-09-07T07:06:53.2002896Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T07:06:53.2003633Z  if not is_valid_experiment_name(exp_name): 2025-09-07T07:06:53.2004695Z  # 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:06:53.2005693Z  continue 2025-09-07T07:06:53.2006132Z  2025-09-07T07:06:53.2006484Z  valid_settings = {} 2025-09-07T07:06:53.2007017Z  for setting in exp_settings: 2025-09-07T07:06:53.2007578Z  if setting not in Experiment._fields: 2025-09-07T07:06:53.2008237Z  log.warning( 2025-09-07T07:06:53.2008951Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T07:06:53.2009628Z  ) 2025-09-07T07:06:53.2010070Z  else: 2025-09-07T07:06:53.2010591Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T07:06:53.2011149Z  2025-09-07T07:06:53.2011610Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T07:06:53.2012234Z  return Settings(experiments) 2025-09-07T07:06:53.2012720Z  2025-09-07T07:06:53.2013066Z  except Exception: 2025-09-07T07:06:53.2013570Z  log.exception("Failed to parse settings") 2025-09-07T07:06:53.2014079Z  2025-09-07T07:06:53.2014424Z  return Settings() 2025-09-07T07:06:53.2014832Z  2025-09-07T07:06:53.2015151Z  2025-09-07T07:06:53.2015702Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T07:06:53.2016267Z  """ 2025-09-07T07:06:53.2016715Z  Parse settings, if any, from the rollout state. 2025-09-07T07:06:53.2017239Z  2025-09-07T07:06:53.2017763Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T07:06:53.2018736Z  and the text below is the list of opted in users. 2025-09-07T07:06:53.2019277Z  2025-09-07T07:06:53.2019842Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T07:06:53.2020531Z  """ 2025-09-07T07:06:53.2021087Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:06:53.2021828Z  return parse_settings_from_text(settings_text) 2025-09-07T07:06:53.2022353Z  2025-09-07T07:06:53.2022662Z  2025-09-07T07:06:53.2023104Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T07:06:53.2023655Z  """ 2025-09-07T07:06:53.2024061Z  Parse users from the rollout state. 2025-09-07T07:06:53.2024547Z  2025-09-07T07:06:53.2024869Z  """ 2025-09-07T07:06:53.2025407Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:06:53.2026137Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T07:06:53.2026803Z  2025-09-07T07:06:53.2027125Z  2025-09-07T07:06:53.2027729Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:06:53.2028702Z  """ 2025-09-07T07:06:53.2029143Z  Check if a user is opted into an experiment 2025-09-07T07:06:53.2029661Z  """ 2025-09-07T07:06:53.2030125Z  return experiment_name in user_optins.get(user, []) 2025-09-07T07:06:53.2030679Z  2025-09-07T07:06:53.2030995Z  2025-09-07T07:06:53.2031602Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:06:53.2032319Z  """ 2025-09-07T07:06:53.2032788Z  Check if a user explicitly opted out of an experiment 2025-09-07T07:06:53.2033342Z  """ 2025-09-07T07:06:53.2033851Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T07:06:53.2034537Z  experiment_optout = "-" + experiment_name 2025-09-07T07:06:53.2035158Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T07:06:53.2035733Z  return False 2025-09-07T07:06:53.2036131Z  2025-09-07T07:06:53.2036581Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T07:06:53.2037160Z  log.warning( 2025-09-07T07:06:53.2038060Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T07:06:53.2038902Z  ) 2025-09-07T07:06:53.2039255Z  2025-09-07T07:06:53.2039595Z  return True 2025-09-07T07:06:53.2039973Z  2025-09-07T07:06:53.2040291Z  2025-09-07T07:06:53.2040631Z def get_runner_prefix( 2025-09-07T07:06:53.2041100Z  rollout_state: str, 2025-09-07T07:06:53.2041588Z  workflow_requestors: Iterable[str], 2025-09-07T07:06:53.2042088Z  branch: str, 2025-09-07T07:06:53.2042607Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T07:06:53.2043270Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T07:06:53.2043835Z  is_canary: bool = False, 2025-09-07T07:06:53.2044284Z ) -> str: 2025-09-07T07:06:53.2044711Z  settings = parse_settings(rollout_state) 2025-09-07T07:06:53.2045281Z  user_optins = parse_users(rollout_state) 2025-09-07T07:06:53.2045779Z  2025-09-07T07:06:53.2046251Z  fleet_prefix = "" 2025-09-07T07:06:53.2046686Z  prefixes = [] 2025-09-07T07:06:53.2047334Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T07:06:53.2048348Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T07:06:53.2049047Z  log.info( 2025-09-07T07:06:53.2049733Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T07:06:53.2050461Z  ) 2025-09-07T07:06:53.2050854Z  continue 2025-09-07T07:06:53.2051255Z  2025-09-07T07:06:53.2051615Z  if opt_out_experiments: 2025-09-07T07:06:53.2052158Z  if experiment_name in opt_out_experiments: 2025-09-07T07:06:53.2052787Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T07:06:53.2053350Z  log.info( 2025-09-07T07:06:53.2054264Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T07:06:53.2055184Z  ) 2025-09-07T07:06:53.2055588Z  continue 2025-09-07T07:06:53.2056016Z  2025-09-07T07:06:53.2056490Z  if eligible_experiments: 2025-09-07T07:06:53.2057063Z  if experiment_name not in eligible_experiments: 2025-09-07T07:06:53.2057687Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T07:06:53.2058557Z  log.info( 2025-09-07T07:06:53.2059358Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T07:06:53.2060157Z  ) 2025-09-07T07:06:53.2060570Z  continue 2025-09-07T07:06:53.2061075Z  elif not experiment_settings.default: 2025-09-07T07:06:53.2061598Z  log.info( 2025-09-07T07:06:53.2062267Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T07:06:53.2062960Z  ) 2025-09-07T07:06:53.2063354Z  continue 2025-09-07T07:06:53.2063756Z  2025-09-07T07:06:53.2064213Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T07:06:53.2064824Z  opted_out_users = [ 2025-09-07T07:06:53.2065290Z  requestor 2025-09-07T07:06:53.2066010Z  for requestor in workflow_requestors 2025-09-07T07:06:53.2067232Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T07:06:53.2068703Z  ] 2025-09-07T07:06:53.2069506Z  2025-09-07T07:06:53.2070210Z  if opted_out_users: 2025-09-07T07:06:53.2071087Z  log.info( 2025-09-07T07:06:53.2072256Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T07:06:53.2073530Z  ) 2025-09-07T07:06:53.2074225Z  continue 2025-09-07T07:06:53.2074972Z  2025-09-07T07:06:53.2075881Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T07:06:53.2077039Z  opted_in_users = [ 2025-09-07T07:06:53.2078089Z  requestor 2025-09-07T07:06:53.2079010Z  for requestor in workflow_requestors 2025-09-07T07:06:53.2080243Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T07:06:53.2081366Z  ] 2025-09-07T07:06:53.2082042Z  2025-09-07T07:06:53.2082685Z  enabled = False 2025-09-07T07:06:53.2083512Z  if opted_in_users: 2025-09-07T07:06:53.2084547Z  log.info( 2025-09-07T07:06:53.2085685Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T07:06:53.2086863Z  ) 2025-09-07T07:06:53.2087581Z  enabled = True 2025-09-07T07:06:53.2088559Z  2025-09-07T07:06:53.2089259Z  elif experiment_settings.rollout_perc: 2025-09-07T07:06:53.2090711Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T07:06:53.2092340Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T07:06:53.2093526Z  log.info( 2025-09-07T07:06:53.2095104Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T07:06:53.2096689Z  ) 2025-09-07T07:06:53.2097403Z  enabled = True 2025-09-07T07:06:53.2098376Z  2025-09-07T07:06:53.2098958Z  if enabled: 2025-09-07T07:06:53.2099719Z  label = experiment_name 2025-09-07T07:06:53.2100700Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T07:06:53.2102023Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T07:06:53.2103924Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T07:06:53.2105247Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T07:06:53.2106440Z  if is_canary: 2025-09-07T07:06:53.2107356Z  label += CANARY_FLEET_SUFFIX 2025-09-07T07:06:53.2108523Z  fleet_prefix = label 2025-09-07T07:06:53.2109382Z  else: 2025-09-07T07:06:53.2110243Z  prefixes.append(label) 2025-09-07T07:06:53.2111137Z  2025-09-07T07:06:53.2111782Z  if len(prefixes) > 1: 2025-09-07T07:06:53.2112602Z  log.error( 2025-09-07T07:06:53.2114436Z  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:06:53.2116339Z  ) 2025-09-07T07:06:53.2117076Z  prefixes = prefixes[:1] 2025-09-07T07:06:53.2118160Z  2025-09-07T07:06:53.2118805Z  # Fleet always comes first 2025-09-07T07:06:53.2119699Z  if fleet_prefix: 2025-09-07T07:06:53.2120449Z  prefixes.insert(0, fleet_prefix) 2025-09-07T07:06:53.2121278Z  2025-09-07T07:06:53.2122021Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T07:06:53.2122994Z  2025-09-07T07:06:53.2123559Z  2025-09-07T07:06:53.2124688Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T07:06:53.2126033Z  """ 2025-09-07T07:06:53.2126945Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T07:06:53.2128329Z  2025-09-07T07:06:53.2129350Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T07:06:53.2130602Z  """ 2025-09-07T07:06:53.2131349Z  gh = get_gh_client(github_token) 2025-09-07T07:06:53.2132327Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T07:06:53.2133442Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T07:06:53.2134436Z  2025-09-07T07:06:53.2134979Z  2025-09-07T07:06:53.2135996Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T07:06:53.2137538Z  for _ in range(num_retries): 2025-09-07T07:06:53.2138588Z  try: 2025-09-07T07:06:53.2139331Z  req = Request(url=url, headers=headers) 2025-09-07T07:06:53.2140473Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T07:06:53.2141597Z  return json.loads(content) 2025-09-07T07:06:53.2142494Z  except Exception as e: 2025-09-07T07:06:53.2143464Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T07:06:53.2144417Z  2025-09-07T07:06:53.2145397Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T07:06:53.2146572Z  return {} 2025-09-07T07:06:53.2147218Z  2025-09-07T07:06:53.2147794Z  2025-09-07T07:06:53.2148617Z @cache 2025-09-07T07:06:53.2149714Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T07:06:53.2151012Z  """ 2025-09-07T07:06:53.2151709Z  Dynamically get PR information 2025-09-07T07:06:53.2152512Z  """ 2025-09-07T07:06:53.2153274Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T07:06:53.2153896Z  headers = { 2025-09-07T07:06:53.2154376Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T07:06:53.2155177Z  "Authorization": f"token {github_token}", 2025-09-07T07:06:53.2155692Z  } 2025-09-07T07:06:53.2156135Z  json_response: dict[str, Any] = download_json( 2025-09-07T07:06:53.2156734Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T07:06:53.2157272Z  headers=headers, 2025-09-07T07:06:53.2157718Z  ) 2025-09-07T07:06:53.2158298Z  2025-09-07T07:06:53.2158878Z  if not json_response: 2025-09-07T07:06:53.2159530Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T07:06:53.2160143Z  return {} 2025-09-07T07:06:53.2160540Z  2025-09-07T07:06:53.2160891Z  return json_response 2025-09-07T07:06:53.2161319Z  2025-09-07T07:06:53.2161638Z  2025-09-07T07:06:53.2162215Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T07:06:53.2162923Z  """ 2025-09-07T07:06:53.2163463Z  Dynamically get the latest list of labels from the pull request 2025-09-07T07:06:53.2164088Z  """ 2025-09-07T07:06:53.2164582Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T07:06:53.2165161Z  return { 2025-09-07T07:06:53.2165745Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T07:06:53.2166530Z  } 2025-09-07T07:06:53.2166874Z  2025-09-07T07:06:53.2167197Z  2025-09-07T07:06:53.2167537Z def main() -> None: 2025-09-07T07:06:53.2168170Z  args = parse_args() 2025-09-07T07:06:53.2168603Z  2025-09-07T07:06:53.2169011Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T07:06:53.2169522Z  2025-09-07T07:06:53.2169891Z  # Check if the PR is opt-out 2025-09-07T07:06:53.2170372Z  if args.pr_number: 2025-09-07T07:06:53.2171048Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T07:06:53.2171765Z  if OPT_OUT_LABEL in labels: 2025-09-07T07:06:53.2172253Z  log.info( 2025-09-07T07:06:53.2172960Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T07:06:53.2173680Z  ) 2025-09-07T07:06:53.2174249Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:06:53.2174899Z  sys.exit() 2025-09-07T07:06:53.2175529Z  2025-09-07T07:06:53.2175893Z  try: 2025-09-07T07:06:53.2176348Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T07:06:53.2177060Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T07:06:53.2177671Z  ) 2025-09-07T07:06:53.2178238Z  2025-09-07T07:06:53.2178637Z  username = get_potential_pr_author( 2025-09-07T07:06:53.2179171Z  args.github_token, 2025-09-07T07:06:53.2179659Z  args.github_repo, 2025-09-07T07:06:53.2180132Z  args.github_actor, 2025-09-07T07:06:53.2180624Z  args.github_ref_type, 2025-09-07T07:06:53.2181120Z  args.github_branch, 2025-09-07T07:06:53.2181576Z  ) 2025-09-07T07:06:53.2182053Z  2025-09-07T07:06:53.2182661Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T07:06:53.2183253Z  2025-09-07T07:06:53.2183659Z  runner_label_prefix = get_runner_prefix( 2025-09-07T07:06:53.2184195Z  rollout_state, 2025-09-07T07:06:53.2184700Z  (args.github_issue_owner, username), 2025-09-07T07:06:53.2185235Z  args.github_branch, 2025-09-07T07:06:53.2185884Z  args.eligible_experiments, 2025-09-07T07:06:53.2186419Z  args.opt_out_experiments, 2025-09-07T07:06:53.2186915Z  is_canary, 2025-09-07T07:06:53.2187342Z  ) 2025-09-07T07:06:53.2187703Z  2025-09-07T07:06:53.2188237Z  except Exception as e: 2025-09-07T07:06:53.2188704Z  log.error( 2025-09-07T07:06:53.2189381Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T07:06:53.2190093Z  ) 2025-09-07T07:06:53.2190456Z  2025-09-07T07:06:53.2190961Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:06:53.2191591Z  2025-09-07T07:06:53.2191908Z  2025-09-07T07:06:53.2192258Z if __name__ == "__main__": 2025-09-07T07:06:53.2192705Z  main() 2025-09-07T07:06:53.2193076Z  2025-09-07T07:06:53.2193396Z EOF 2025-09-07T07:06:53.2193736Z  2025-09-07T07:06:53.2194087Z cat runner_determinator.py 2025-09-07T07:06:53.3725652Z shell: /usr/bin/bash -e {0} 2025-09-07T07:06:53.3726591Z env: 2025-09-07T07:06:53.3727328Z GITHUB_TOKEN: *** 2025-09-07T07:06:53.3727808Z ISSUE_NUMBER: 5132 2025-09-07T07:06:53.3728576Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:06:53.3729145Z ISSUE_OWNER: 2025-09-07T07:06:53.3729589Z CHECK_EXPERIMENTS: 2025-09-07T07:06:53.3730083Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:06:53.3730582Z PR_NUMBER: 2025-09-07T07:06:53.3730996Z ##[endgroup] 2025-09-07T07:06:53.3933677Z # flake8: noqa: G004 2025-09-07T07:06:53.3934001Z 2025-09-07T07:06:53.3934409Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T07:06:53.3935278Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T07:06:53.3936022Z # python .github/scripts/update_runner_determinator.py 2025-09-07T07:06:53.3936454Z 2025-09-07T07:06:53.3936602Z """ 2025-09-07T07:06:53.3937138Z This runner determinator is used to determine which set of runners to run a 2025-09-07T07:06:53.3938199Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T07:06:53.3939078Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T07:06:53.3939851Z of which runners should be used to run which job. 2025-09-07T07:06:53.3940220Z 2025-09-07T07:06:53.3940578Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T07:06:53.3941602Z separated by a line containing "---". If the line is not present, the 2025-09-07T07:06:53.3942446Z settings are considered to be empty with only the second part, the user 2025-09-07T07:06:53.3943087Z list, defined. 2025-09-07T07:06:53.3943306Z 2025-09-07T07:06:53.3943645Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T07:06:53.3944479Z used to define any settings that are needed to determine which runners to use. 2025-09-07T07:06:53.3945261Z It's fields are defined by the RolloutSettings class below. 2025-09-07T07:06:53.3945670Z 2025-09-07T07:06:53.3946015Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T07:06:53.3946816Z The user list is also a comma separated list of additional features or 2025-09-07T07:06:53.3947491Z experiments which the user could be opted in to. 2025-09-07T07:06:53.3947862Z 2025-09-07T07:06:53.3948663Z The user list has the following rules: 2025-09-07T07:06:53.3949015Z 2025-09-07T07:06:53.3949321Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T07:06:53.3950120Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T07:06:53.3950831Z - A "#" prefix opts the user out of all experiments 2025-09-07T07:06:53.3951196Z 2025-09-07T07:06:53.3951363Z Example config: 2025-09-07T07:06:53.3951781Z # A list of experiments that can be opted into. 2025-09-07T07:06:53.3952577Z # This defines the behavior they'll induce when opted into. 2025-09-07T07:06:53.3953147Z # Expected syntax is: 2025-09-07T07:06:53.3953748Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T07:06:53.3954646Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T07:06:53.3955213Z 2025-09-07T07:06:53.3955370Z experiments: 2025-09-07T07:06:53.3955733Z lf: 2025-09-07T07:06:53.3956074Z rollout_percent: 25 2025-09-07T07:06:53.3956496Z all_branches: false 2025-09-07T07:06:53.3956899Z default: true 2025-09-07T07:06:53.3957274Z --- 2025-09-07T07:06:53.3957457Z 2025-09-07T07:06:53.3957608Z # Opt-ins: 2025-09-07T07:06:53.3958371Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T07:06:53.3959184Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T07:06:53.3959914Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T07:06:53.3960523Z # Experiments should be from the above list. 2025-09-07T07:06:53.3960873Z 2025-09-07T07:06:53.3961038Z @User1,-lf,split_build 2025-09-07T07:06:53.3961440Z @User2,lf 2025-09-07T07:06:53.3961785Z @User3,split_build 2025-09-07T07:06:53.3962160Z """ 2025-09-07T07:06:53.3962336Z 2025-09-07T07:06:53.3962485Z import json 2025-09-07T07:06:53.3962824Z import logging 2025-09-07T07:06:53.3963166Z import os 2025-09-07T07:06:53.3963503Z import random 2025-09-07T07:06:53.3963852Z import re 2025-09-07T07:06:53.3964175Z import sys 2025-09-07T07:06:53.3964542Z from argparse import ArgumentParser 2025-09-07T07:06:53.3965019Z from collections.abc import Iterable 2025-09-07T07:06:53.3965500Z from functools import cache 2025-09-07T07:06:53.3965931Z from logging import LogRecord 2025-09-07T07:06:53.3966380Z from typing import Any, NamedTuple 2025-09-07T07:06:53.3966858Z from urllib.request import Request, urlopen 2025-09-07T07:06:53.3967219Z 2025-09-07T07:06:53.3967363Z import yaml 2025-09-07T07:06:53.3967714Z from github import Auth, Github 2025-09-07T07:06:53.3968464Z from github.Issue import Issue 2025-09-07T07:06:53.3968781Z 2025-09-07T07:06:53.3968787Z 2025-09-07T07:06:53.3968990Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T07:06:53.3969618Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T07:06:53.3970424Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T07:06:53.3970936Z 2025-09-07T07:06:53.3971146Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T07:06:53.3971809Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T07:06:53.3972282Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T07:06:53.3972793Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T07:06:53.3973114Z 2025-09-07T07:06:53.3973299Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T07:06:53.3973601Z 2025-09-07T07:06:53.3973774Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T07:06:53.3974209Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T07:06:53.3974467Z 2025-09-07T07:06:53.3974473Z 2025-09-07T07:06:53.3974650Z class Experiment(NamedTuple): 2025-09-07T07:06:53.3975087Z rollout_perc: float = ( 2025-09-07T07:06:53.3975665Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T07:06:53.3976289Z ) 2025-09-07T07:06:53.3976629Z all_branches: bool = ( 2025-09-07T07:06:53.3977199Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T07:06:53.3977820Z ) 2025-09-07T07:06:53.3978436Z default: bool = ( 2025-09-07T07:06:53.3979005Z True # If True, the experiment is enabled by default for all queries 2025-09-07T07:06:53.3979592Z ) 2025-09-07T07:06:53.3979772Z 2025-09-07T07:06:53.3979936Z # Add more fields as needed 2025-09-07T07:06:53.3980208Z 2025-09-07T07:06:53.3980214Z 2025-09-07T07:06:53.3980391Z class Settings(NamedTuple): 2025-09-07T07:06:53.3980800Z """ 2025-09-07T07:06:53.3981354Z Settings for the experiments that can be opted into. 2025-09-07T07:06:53.3981881Z """ 2025-09-07T07:06:53.3982061Z 2025-09-07T07:06:53.3982258Z experiments: dict[str, Experiment] = {} 2025-09-07T07:06:53.3982589Z 2025-09-07T07:06:53.3982596Z 2025-09-07T07:06:53.3982787Z class ColorFormatter(logging.Formatter): 2025-09-07T07:06:53.3983364Z """Color codes the log messages based on the log level""" 2025-09-07T07:06:53.3983763Z 2025-09-07T07:06:53.3983919Z COLORS = { 2025-09-07T07:06:53.3984279Z "WARNING": "\033[33m", # Yellow 2025-09-07T07:06:53.3984750Z "ERROR": "\033[31m", # Red 2025-09-07T07:06:53.3985200Z "CRITICAL": "\033[31m", # Red 2025-09-07T07:06:53.3985658Z "INFO": "\033[0m", # Reset 2025-09-07T07:06:53.3986093Z "DEBUG": "\033[0m", # Reset 2025-09-07T07:06:53.3986523Z } 2025-09-07T07:06:53.3986699Z 2025-09-07T07:06:53.3986901Z def format(self, record: LogRecord) -> str: 2025-09-07T07:06:53.3987595Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T07:06:53.3988490Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T07:06:53.3989025Z return super().format(record) 2025-09-07T07:06:53.3989334Z 2025-09-07T07:06:53.3989341Z 2025-09-07T07:06:53.3989523Z handler = logging.StreamHandler() 2025-09-07T07:06:53.3990167Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T07:06:53.3990684Z 2025-09-07T07:06:53.3990910Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T07:06:53.3991444Z log.addHandler(handler) 2025-09-07T07:06:53.3991863Z log.setLevel(logging.INFO) 2025-09-07T07:06:53.3992154Z 2025-09-07T07:06:53.3992161Z 2025-09-07T07:06:53.3992396Z def set_github_output(key: str, value: str) -> None: 2025-09-07T07:06:53.3992904Z """ 2025-09-07T07:06:53.3993362Z Defines outputs of the github action that invokes this script 2025-09-07T07:06:53.3993936Z """ 2025-09-07T07:06:53.3994266Z if not GITHUB_OUTPUT: 2025-09-07T07:06:53.3995252Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T07:06:53.3996308Z log.warning( 2025-09-07T07:06:53.3997094Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T07:06:53.3998137Z ) 2025-09-07T07:06:53.4007663Z print(f"::set-output name={key}::{value}") 2025-09-07T07:06:53.4008481Z return 2025-09-07T07:06:53.4008700Z 2025-09-07T07:06:53.4009074Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T07:06:53.4009627Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T07:06:53.4010159Z f.write(f"{key}={value}\n") 2025-09-07T07:06:53.4010459Z 2025-09-07T07:06:53.4010466Z 2025-09-07T07:06:53.4010751Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T07:06:53.4011343Z return frozenset( 2025-09-07T07:06:53.4011915Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T07:06:53.4012537Z ) 2025-09-07T07:06:53.4012718Z 2025-09-07T07:06:53.4012725Z 2025-09-07T07:06:53.4012899Z def parse_args() -> Any: 2025-09-07T07:06:53.4013406Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T07:06:53.4014201Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T07:06:53.4014918Z parser.add_argument( 2025-09-07T07:06:53.4015328Z "--github-issue-repo", 2025-09-07T07:06:53.4015759Z type=str, 2025-09-07T07:06:53.4016126Z required=False, 2025-09-07T07:06:53.4016542Z default="pytorch/test-infra", 2025-09-07T07:06:53.4017029Z help="GitHub repo to get the issue", 2025-09-07T07:06:53.4017494Z ) 2025-09-07T07:06:53.4017822Z parser.add_argument( 2025-09-07T07:06:53.4018406Z "--github-repo", 2025-09-07T07:06:53.4018791Z type=str, 2025-09-07T07:06:53.4019154Z required=True, 2025-09-07T07:06:53.4019705Z help="GitHub repo where CI is running", 2025-09-07T07:06:53.4020190Z ) 2025-09-07T07:06:53.4020527Z parser.add_argument( 2025-09-07T07:06:53.4021085Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T07:06:53.4021698Z ) 2025-09-07T07:06:53.4022032Z parser.add_argument( 2025-09-07T07:06:53.4022620Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T07:06:53.4023236Z ) 2025-09-07T07:06:53.4023570Z parser.add_argument( 2025-09-07T07:06:53.4024153Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T07:06:53.4024787Z ) 2025-09-07T07:06:53.4025126Z parser.add_argument( 2025-09-07T07:06:53.4025722Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T07:06:53.4026378Z ) 2025-09-07T07:06:53.4026703Z parser.add_argument( 2025-09-07T07:06:53.4027106Z "--github-ref-type", 2025-09-07T07:06:53.4027516Z type=str, 2025-09-07T07:06:53.4028044Z required=True, 2025-09-07T07:06:53.4028512Z help="Current GitHub ref type, branch or tag", 2025-09-07T07:06:53.4029020Z ) 2025-09-07T07:06:53.4029346Z parser.add_argument( 2025-09-07T07:06:53.4029766Z "--eligible-experiments", 2025-09-07T07:06:53.4030222Z type=_str_comma_separated_to_set, 2025-09-07T07:06:53.4030691Z required=False, 2025-09-07T07:06:53.4031065Z default="", 2025-09-07T07:06:53.4031853Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T07:06:53.4032704Z ) 2025-09-07T07:06:53.4033033Z parser.add_argument( 2025-09-07T07:06:53.4033445Z "--opt-out-experiments", 2025-09-07T07:06:53.4033899Z type=_str_comma_separated_to_set, 2025-09-07T07:06:53.4034374Z required=False, 2025-09-07T07:06:53.4034745Z default="", 2025-09-07T07:06:53.4035101Z help=( 2025-09-07T07:06:53.4035716Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T07:06:53.4036758Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T07:06:53.4037521Z ), 2025-09-07T07:06:53.4037834Z ) 2025-09-07T07:06:53.4038401Z parser.add_argument( 2025-09-07T07:06:53.4038798Z "--pr-number", 2025-09-07T07:06:53.4039175Z type=str, 2025-09-07T07:06:53.4039526Z required=False, 2025-09-07T07:06:53.4039903Z default="", 2025-09-07T07:06:53.4040442Z help="the optional PR number where this is run", 2025-09-07T07:06:53.4040959Z ) 2025-09-07T07:06:53.4041130Z 2025-09-07T07:06:53.4041311Z return parser.parse_args() 2025-09-07T07:06:53.4041591Z 2025-09-07T07:06:53.4041597Z 2025-09-07T07:06:53.4041975Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T07:06:53.4042676Z auth = Auth.Token(github_token) 2025-09-07T07:06:53.4043133Z return Github(auth=auth) 2025-09-07T07:06:53.4043408Z 2025-09-07T07:06:53.4043415Z 2025-09-07T07:06:53.4043827Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T07:06:53.4044555Z repo = gh.get_repo(repo) 2025-09-07T07:06:53.4045002Z return repo.get_issue(number=issue_num) 2025-09-07T07:06:53.4045337Z 2025-09-07T07:06:53.4045343Z 2025-09-07T07:06:53.4045514Z def get_potential_pr_author( 2025-09-07T07:06:53.4092177Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T07:06:53.4092940Z ) -> str: 2025-09-07T07:06:53.4093433Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T07:06:53.4094188Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T07:06:53.4094866Z # embedded in the tag name: ciflow// 2025-09-07T07:06:53.4095264Z 2025-09-07T07:06:53.4095619Z gh = get_gh_client(github_token) 2025-09-07T07:06:53.4095933Z 2025-09-07T07:06:53.4096187Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T07:06:53.4096765Z split_tag = ref_name.split("/") 2025-09-07T07:06:53.4097221Z if ( 2025-09-07T07:06:53.4097568Z len(split_tag) == 3 2025-09-07T07:06:53.4098128Z and split_tag[0] == "ciflow" 2025-09-07T07:06:53.4098617Z and split_tag[2].isnumeric() 2025-09-07T07:06:53.4099090Z ): 2025-09-07T07:06:53.4099441Z pr_number = split_tag[2] 2025-09-07T07:06:53.4099893Z try: 2025-09-07T07:06:53.4100276Z repository = gh.get_repo(repo) 2025-09-07T07:06:53.4100845Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T07:06:53.4101397Z except Exception as e: 2025-09-07T07:06:53.4101869Z raise Exception( # noqa: TRY002 2025-09-07T07:06:53.4102487Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T07:06:53.4103083Z ) from e 2025-09-07T07:06:53.4103581Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T07:06:53.4104210Z # In all other cases, return the original input username 2025-09-07T07:06:53.4104750Z return username 2025-09-07T07:06:53.4104966Z 2025-09-07T07:06:53.4104972Z 2025-09-07T07:06:53.4105174Z def is_exception_branch(branch: str) -> bool: 2025-09-07T07:06:53.4105666Z """ 2025-09-07T07:06:53.4106266Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T07:06:53.4106974Z """ 2025-09-07T07:06:53.4107481Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T07:06:53.4108074Z 2025-09-07T07:06:53.4108081Z 2025-09-07T07:06:53.4108264Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T07:06:53.4108721Z try: 2025-09-07T07:06:53.4109068Z data = yaml.safe_load(yaml_text) 2025-09-07T07:06:53.4109546Z return data 2025-09-07T07:06:53.4109926Z except yaml.YAMLError: 2025-09-07T07:06:53.4110360Z log.exception("Error loading YAML") 2025-09-07T07:06:53.4110825Z raise 2025-09-07T07:06:53.4111022Z 2025-09-07T07:06:53.4111028Z 2025-09-07T07:06:53.4111410Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T07:06:53.4112084Z """ 2025-09-07T07:06:53.4112657Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T07:06:53.4113219Z 2025-09-07T07:06:53.4113667Z If the issue body contains "---" then the text above that is the settings 2025-09-07T07:06:53.4114370Z and the text below is the list of opted in users. 2025-09-07T07:06:53.4114744Z 2025-09-07T07:06:53.4115087Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T07:06:53.4115727Z """ 2025-09-07T07:06:53.4116121Z rollout_state_parts = rollout_state.split("---") 2025-09-07T07:06:53.4116680Z if len(rollout_state_parts) >= 2: 2025-09-07T07:06:53.4117226Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T07:06:53.4117759Z else: 2025-09-07T07:06:53.4118234Z return "", rollout_state 2025-09-07T07:06:53.4118525Z 2025-09-07T07:06:53.4118532Z 2025-09-07T07:06:53.4118715Z class UserOptins(dict[str, list[str]]): 2025-09-07T07:06:53.4119178Z """ 2025-09-07T07:06:53.4119647Z Dictionary of users with a list of features they have opted into 2025-09-07T07:06:53.4120239Z """ 2025-09-07T07:06:53.4120413Z 2025-09-07T07:06:53.4120419Z 2025-09-07T07:06:53.4120733Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T07:06:53.4121334Z """ 2025-09-07T07:06:53.4121976Z 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:06:53.4122618Z 2025-09-07T07:06:53.4123189Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T07:06:53.4124250Z - Example line: "@User1,lf,split_build" 2025-09-07T07:06:53.4124874Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T07:06:53.4125319Z 2025-09-07T07:06:53.4125325Z 2025-09-07T07:06:53.4125466Z """ 2025-09-07T07:06:53.4125825Z optins = UserOptins() 2025-09-07T07:06:53.4126282Z for user in user_optin_text.split("\n"): 2025-09-07T07:06:53.4126780Z user = user.strip("\r\n\t -") 2025-09-07T07:06:53.4127269Z if not user or not user.startswith("@"): 2025-09-07T07:06:53.4127795Z # Not a valid user. Skip 2025-09-07T07:06:53.4128389Z continue 2025-09-07T07:06:53.4128615Z 2025-09-07T07:06:53.4128760Z if user: 2025-09-07T07:06:53.4129147Z usr_name = user.split(",")[0].strip("@") 2025-09-07T07:06:53.4129780Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T07:06:53.4130232Z 2025-09-07T07:06:53.4130392Z return optins 2025-09-07T07:06:53.4130608Z 2025-09-07T07:06:53.4130615Z 2025-09-07T07:06:53.4130873Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T07:06:53.4131450Z """ 2025-09-07T07:06:53.4131800Z Check if the experiment name is valid. 2025-09-07T07:06:53.4132274Z A valid name: 2025-09-07T07:06:53.4132845Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T07:06:53.4133709Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T07:06:53.4134356Z - Cannot contain spaces 2025-09-07T07:06:53.4134776Z """ 2025-09-07T07:06:53.4134952Z 2025-09-07T07:06:53.4135196Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T07:06:53.4135829Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T07:06:53.4136240Z 2025-09-07T07:06:53.4136384Z if valid: 2025-09-07T07:06:53.4136718Z return True 2025-09-07T07:06:53.4136942Z 2025-09-07T07:06:53.4137088Z log.error( 2025-09-07T07:06:53.4138553Z 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:06:53.4140033Z ) 2025-09-07T07:06:53.4140357Z return False 2025-09-07T07:06:53.4140567Z 2025-09-07T07:06:53.4140573Z 2025-09-07T07:06:53.4140848Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T07:06:53.4141411Z """ 2025-09-07T07:06:53.4142081Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T07:06:53.4142745Z """ 2025-09-07T07:06:53.4143059Z try: 2025-09-07T07:06:53.4143390Z if settings_text: 2025-09-07T07:06:53.4144053Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T07:06:53.4144773Z # for easy reading 2025-09-07T07:06:53.4145497Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T07:06:53.4146306Z # the backtick character in shell commands. 2025-09-07T07:06:53.4146860Z backtick = chr(96) # backtick character 2025-09-07T07:06:53.4147465Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T07:06:53.4148172Z settings = load_yaml(settings_text) 2025-09-07T07:06:53.4148521Z 2025-09-07T07:06:53.4148893Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T07:06:53.4149576Z experiments = {} 2025-09-07T07:06:53.4149851Z 2025-09-07T07:06:53.4150196Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T07:06:53.4150881Z if not is_valid_experiment_name(exp_name): 2025-09-07T07:06:53.4151892Z # 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:06:53.4152982Z continue 2025-09-07T07:06:53.4153237Z 2025-09-07T07:06:53.4153401Z valid_settings = {} 2025-09-07T07:06:53.4153872Z for setting in exp_settings: 2025-09-07T07:06:53.4154382Z if setting not in Experiment._fields: 2025-09-07T07:06:53.4154880Z log.warning( 2025-09-07T07:06:53.4155521Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T07:06:53.4156180Z ) 2025-09-07T07:06:53.4156569Z else: 2025-09-07T07:06:53.4157024Z valid_settings[setting] = exp_settings[setting] 2025-09-07T07:06:53.4157413Z 2025-09-07T07:06:53.4157660Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T07:06:53.4158621Z return Settings(experiments) 2025-09-07T07:06:53.4158958Z 2025-09-07T07:06:53.4159114Z except Exception: 2025-09-07T07:06:53.4159548Z log.exception("Failed to parse settings") 2025-09-07T07:06:53.4159904Z 2025-09-07T07:06:53.4160061Z return Settings() 2025-09-07T07:06:53.4160290Z 2025-09-07T07:06:53.4160296Z 2025-09-07T07:06:53.4160532Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T07:06:53.4161054Z """ 2025-09-07T07:06:53.4161446Z Parse settings, if any, from the rollout state. 2025-09-07T07:06:53.4161814Z 2025-09-07T07:06:53.4162136Z If the issue body contains "---" then the text above that is the settings 2025-09-07T07:06:53.4162839Z and the text below is the list of opted in users. 2025-09-07T07:06:53.4163206Z 2025-09-07T07:06:53.4163578Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T07:06:53.4164244Z """ 2025-09-07T07:06:53.4164743Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:06:53.4165425Z return parse_settings_from_text(settings_text) 2025-09-07T07:06:53.4165788Z 2025-09-07T07:06:53.4165795Z 2025-09-07T07:06:53.4166015Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T07:06:53.4166546Z """ 2025-09-07T07:06:53.4166891Z Parse users from the rollout state. 2025-09-07T07:06:53.4167208Z 2025-09-07T07:06:53.4167353Z """ 2025-09-07T07:06:53.4167826Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:06:53.4168602Z return parse_user_opt_in_from_text(users_text) 2025-09-07T07:06:53.4168960Z 2025-09-07T07:06:53.4168966Z 2025-09-07T07:06:53.4169471Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:06:53.4170157Z """ 2025-09-07T07:06:53.4170532Z Check if a user is opted into an experiment 2025-09-07T07:06:53.4171013Z """ 2025-09-07T07:06:53.4171417Z return experiment_name in user_optins.get(user, []) 2025-09-07T07:06:53.4171804Z 2025-09-07T07:06:53.4171815Z 2025-09-07T07:06:53.4172195Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:06:53.4172865Z """ 2025-09-07T07:06:53.4173272Z Check if a user explicitly opted out of an experiment 2025-09-07T07:06:53.4173800Z """ 2025-09-07T07:06:53.4174251Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T07:06:53.4174875Z experiment_optout = "-" + experiment_name 2025-09-07T07:06:53.4175452Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T07:06:53.4175996Z return False 2025-09-07T07:06:53.4176218Z 2025-09-07T07:06:53.4176465Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T07:06:53.4176997Z log.warning( 2025-09-07T07:06:53.4177720Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T07:06:53.4178655Z ) 2025-09-07T07:06:53.4178849Z 2025-09-07T07:06:53.4178994Z return True 2025-09-07T07:06:53.4179330Z 2025-09-07T07:06:53.4179336Z 2025-09-07T07:06:53.4179502Z def get_runner_prefix( 2025-09-07T07:06:53.4179892Z rollout_state: str, 2025-09-07T07:06:53.4180307Z workflow_requestors: Iterable[str], 2025-09-07T07:06:53.4180764Z branch: str, 2025-09-07T07:06:53.4181204Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T07:06:53.4181803Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T07:06:53.4182332Z is_canary: bool = False, 2025-09-07T07:06:53.4182731Z ) -> str: 2025-09-07T07:06:53.4183102Z settings = parse_settings(rollout_state) 2025-09-07T07:06:53.4183626Z user_optins = parse_users(rollout_state) 2025-09-07T07:06:53.4183960Z 2025-09-07T07:06:53.4184115Z fleet_prefix = "" 2025-09-07T07:06:53.4184490Z prefixes = [] 2025-09-07T07:06:53.4185050Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T07:06:53.4185903Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T07:06:53.4186541Z log.info( 2025-09-07T07:06:53.4187151Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T07:06:53.4187845Z ) 2025-09-07T07:06:53.4188285Z continue 2025-09-07T07:06:53.4188501Z 2025-09-07T07:06:53.4188674Z if opt_out_experiments: 2025-09-07T07:06:53.4189143Z if experiment_name in opt_out_experiments: 2025-09-07T07:06:53.4189715Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T07:06:53.4190234Z log.info( 2025-09-07T07:06:53.4191079Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T07:06:53.4191995Z ) 2025-09-07T07:06:53.4192347Z continue 2025-09-07T07:06:53.4192582Z 2025-09-07T07:06:53.4192757Z if eligible_experiments: 2025-09-07T07:06:53.4193249Z if experiment_name not in eligible_experiments: 2025-09-07T07:06:53.4193829Z exp_list = ", ".join(eligible_experiments) 2025-09-07T07:06:53.4194321Z log.info( 2025-09-07T07:06:53.4195026Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T07:06:53.4195773Z ) 2025-09-07T07:06:53.4196124Z continue 2025-09-07T07:06:53.4196547Z elif not experiment_settings.default: 2025-09-07T07:06:53.4197030Z log.info( 2025-09-07T07:06:53.4197737Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T07:06:53.4198509Z ) 2025-09-07T07:06:53.4198848Z continue 2025-09-07T07:06:53.4199068Z 2025-09-07T07:06:53.4199318Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T07:06:53.4199876Z opted_out_users = [ 2025-09-07T07:06:53.4200271Z requestor 2025-09-07T07:06:53.4200686Z for requestor in workflow_requestors 2025-09-07T07:06:53.4201293Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T07:06:53.4201856Z ] 2025-09-07T07:06:53.4202047Z 2025-09-07T07:06:53.4202213Z if opted_out_users: 2025-09-07T07:06:53.4202615Z log.info( 2025-09-07T07:06:53.4203172Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T07:06:53.4203801Z ) 2025-09-07T07:06:53.4204137Z continue 2025-09-07T07:06:53.4204353Z 2025-09-07T07:06:53.4204599Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T07:06:53.4205144Z opted_in_users = [ 2025-09-07T07:06:53.4205537Z requestor 2025-09-07T07:06:53.4205941Z for requestor in workflow_requestors 2025-09-07T07:06:53.4206532Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T07:06:53.4207077Z ] 2025-09-07T07:06:53.4207381Z 2025-09-07T07:06:53.4207531Z enabled = False 2025-09-07T07:06:53.4208019Z if opted_in_users: 2025-09-07T07:06:53.4208417Z log.info( 2025-09-07T07:06:53.4208951Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T07:06:53.4209564Z ) 2025-09-07T07:06:53.4209905Z enabled = True 2025-09-07T07:06:53.4210153Z 2025-09-07T07:06:53.4210345Z elif experiment_settings.rollout_perc: 2025-09-07T07:06:53.4211102Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T07:06:53.4211953Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T07:06:53.4212539Z log.info( 2025-09-07T07:06:53.4213331Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T07:06:53.4214166Z ) 2025-09-07T07:06:53.4214538Z enabled = True 2025-09-07T07:06:53.4214800Z 2025-09-07T07:06:53.4214941Z if enabled: 2025-09-07T07:06:53.4215314Z label = experiment_name 2025-09-07T07:06:53.4215799Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T07:06:53.4216538Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T07:06:53.4217323Z # - If it's enabled, then we always list it's prefix first 2025-09-07T07:06:53.4218100Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T07:06:53.4218702Z if is_canary: 2025-09-07T07:06:53.4219134Z label += CANARY_FLEET_SUFFIX 2025-09-07T07:06:53.4219632Z fleet_prefix = label 2025-09-07T07:06:53.4220063Z else: 2025-09-07T07:06:53.4220441Z prefixes.append(label) 2025-09-07T07:06:53.4220749Z 2025-09-07T07:06:53.4220912Z if len(prefixes) > 1: 2025-09-07T07:06:53.4221306Z log.error( 2025-09-07T07:06:53.4222242Z 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:06:53.4223252Z ) 2025-09-07T07:06:53.4223602Z prefixes = prefixes[:1] 2025-09-07T07:06:53.4223877Z 2025-09-07T07:06:53.4224047Z # Fleet always comes first 2025-09-07T07:06:53.4224469Z if fleet_prefix: 2025-09-07T07:06:53.4224865Z prefixes.insert(0, fleet_prefix) 2025-09-07T07:06:53.4225195Z 2025-09-07T07:06:53.4225536Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T07:06:53.4225914Z 2025-09-07T07:06:53.4225921Z 2025-09-07T07:06:53.4226325Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T07:06:53.4227025Z """ 2025-09-07T07:06:53.4227551Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T07:06:53.4228165Z 2025-09-07T07:06:53.4228516Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T07:06:53.4229149Z """ 2025-09-07T07:06:53.4229488Z gh = get_gh_client(github_token) 2025-09-07T07:06:53.4229971Z issue = get_issue(gh, repo, issue_num) 2025-09-07T07:06:53.4230540Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T07:06:53.4230935Z 2025-09-07T07:06:53.4230942Z 2025-09-07T07:06:53.4231307Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T07:06:53.4231984Z for _ in range(num_retries): 2025-09-07T07:06:53.4232414Z try: 2025-09-07T07:06:53.4232800Z req = Request(url=url, headers=headers) 2025-09-07T07:06:53.4233397Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T07:06:53.4233970Z return json.loads(content) 2025-09-07T07:06:53.4234442Z except Exception as e: 2025-09-07T07:06:53.4234914Z log.warning(f"Could not download {url}: {e}") 2025-09-07T07:06:53.4235401Z 2025-09-07T07:06:53.4235747Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T07:06:53.4236383Z return {} 2025-09-07T07:06:53.4236581Z 2025-09-07T07:06:53.4236588Z 2025-09-07T07:06:53.4236725Z @cache 2025-09-07T07:06:53.4237276Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T07:06:53.4238057Z """ 2025-09-07T07:06:53.4238405Z Dynamically get PR information 2025-09-07T07:06:53.4238833Z """ 2025-09-07T07:06:53.4239277Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T07:06:53.4239828Z headers = { 2025-09-07T07:06:53.4240234Z "Accept": "application/vnd.github.v3+json", 2025-09-07T07:06:53.4240779Z "Authorization": f"token {github_token}", 2025-09-07T07:06:53.4241253Z } 2025-09-07T07:06:53.4241625Z json_response: dict[str, Any] = download_json( 2025-09-07T07:06:53.4242175Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T07:06:53.4242672Z headers=headers, 2025-09-07T07:06:53.4243046Z ) 2025-09-07T07:06:53.4243217Z 2025-09-07T07:06:53.4243384Z if not json_response: 2025-09-07T07:06:53.4243883Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T07:06:53.4244435Z return {} 2025-09-07T07:06:53.4244644Z 2025-09-07T07:06:53.4244802Z return json_response 2025-09-07T07:06:53.4245054Z 2025-09-07T07:06:53.4245060Z 2025-09-07T07:06:53.4245421Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T07:06:53.4246070Z """ 2025-09-07T07:06:53.4246537Z Dynamically get the latest list of labels from the pull request 2025-09-07T07:06:53.4247126Z """ 2025-09-07T07:06:53.4247550Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T07:06:53.4248195Z return { 2025-09-07T07:06:53.4248707Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T07:06:53.4249339Z } 2025-09-07T07:06:53.4249514Z 2025-09-07T07:06:53.4249520Z 2025-09-07T07:06:53.4249673Z def main() -> None: 2025-09-07T07:06:53.4250040Z args = parse_args() 2025-09-07T07:06:53.4250274Z 2025-09-07T07:06:53.4250476Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T07:06:53.4250814Z 2025-09-07T07:06:53.4250984Z # Check if the PR is opt-out 2025-09-07T07:06:53.4251416Z if args.pr_number: 2025-09-07T07:06:53.4251997Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T07:06:53.4252793Z if OPT_OUT_LABEL in labels: 2025-09-07T07:06:53.4253233Z log.info( 2025-09-07T07:06:53.4253849Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T07:06:53.4254529Z ) 2025-09-07T07:06:53.4255016Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:06:53.4255617Z sys.exit() 2025-09-07T07:06:53.4255846Z 2025-09-07T07:06:53.4255991Z try: 2025-09-07T07:06:53.4256376Z rollout_state = get_rollout_state_from_issue( 2025-09-07T07:06:53.4256999Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T07:06:53.4257570Z ) 2025-09-07T07:06:53.4257748Z 2025-09-07T07:06:53.4258027Z username = get_potential_pr_author( 2025-09-07T07:06:53.4258519Z args.github_token, 2025-09-07T07:06:53.4258937Z args.github_repo, 2025-09-07T07:06:53.4259361Z args.github_actor, 2025-09-07T07:06:53.4259786Z args.github_ref_type, 2025-09-07T07:06:53.4260221Z args.github_branch, 2025-09-07T07:06:53.4260623Z ) 2025-09-07T07:06:53.4260800Z 2025-09-07T07:06:53.4261052Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T07:06:53.4261459Z 2025-09-07T07:06:53.4261652Z runner_label_prefix = get_runner_prefix( 2025-09-07T07:06:53.4262135Z rollout_state, 2025-09-07T07:06:53.4262686Z (args.github_issue_owner, username), 2025-09-07T07:06:53.4263175Z args.github_branch, 2025-09-07T07:06:53.4263613Z args.eligible_experiments, 2025-09-07T07:06:53.4264092Z args.opt_out_experiments, 2025-09-07T07:06:53.4264528Z is_canary, 2025-09-07T07:06:53.4264892Z ) 2025-09-07T07:06:53.4265072Z 2025-09-07T07:06:53.4265233Z except Exception as e: 2025-09-07T07:06:53.4265628Z log.error( 2025-09-07T07:06:53.4266222Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T07:06:53.4266918Z ) 2025-09-07T07:06:53.4267096Z 2025-09-07T07:06:53.4267395Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:06:53.4267842Z 2025-09-07T07:06:53.4267848Z 2025-09-07T07:06:53.4268106Z if __name__ == "__main__": 2025-09-07T07:06:53.4268499Z main() 2025-09-07T07:06:53.4268683Z 2025-09-07T07:06:53.4354973Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T07:06:53.4355866Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T07:06:53.4385679Z shell: /usr/bin/bash -e {0} 2025-09-07T07:06:53.4386127Z env: 2025-09-07T07:06:53.4386688Z GITHUB_TOKEN: *** 2025-09-07T07:06:53.4387063Z ISSUE_NUMBER: 5132 2025-09-07T07:06:53.4387455Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:06:53.4388025Z ISSUE_OWNER: 2025-09-07T07:06:53.4388386Z CHECK_EXPERIMENTS: 2025-09-07T07:06:53.4388792Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:06:53.4389181Z PR_NUMBER: 2025-09-07T07:06:53.4389519Z ##[endgroup] 2025-09-07T07:06:55.9213416Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T07:06:57.2373145Z Collecting urllib3==1.26.18 2025-09-07T07:06:57.2738794Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T07:06:57.2980938Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.9 MB/s eta 0:00:00 2025-09-07T07:06:57.3204289Z Collecting PyGithub==2.3.0 2025-09-07T07:06:57.3254463Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T07:06:57.3670713Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T07:06:57.3706782Z 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:06:57.3749338Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-09-07T07:06:57.3761349Z 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:06:57.3780051Z 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:06:57.4034564Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T07:06:57.4071330Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T07:06:57.4356766Z 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:06:57.5624921Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T07:06:57.5662512Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T07:06:57.6862230Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T07:06:57.6904302Z 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:06:57.7092527Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T07:06:57.7129074Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T07:06:57.7405481Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T07:06:57.7483747Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 22.1 MB/s eta 0:00:00 2025-09-07T07:06:57.7524765Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T07:06:57.7658832Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 29.5 MB/s eta 0:00:00 2025-09-07T07:06:57.7694975Z 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:06:57.7919561Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 40.7 MB/s eta 0:00:00 2025-09-07T07:06:57.7954252Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T07:06:57.8012172Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T07:06:57.8127103Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 47.4 MB/s eta 0:00:00 2025-09-07T07:06:57.8162474Z 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:06:57.8204938Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 29.1 MB/s eta 0:00:00 2025-09-07T07:06:57.8259053Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T07:06:57.8299034Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 44.1 MB/s eta 0:00:00 2025-09-07T07:06:58.1747533Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T07:06:58.7057193Z 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:06:58.7863741Z ##[group]Run curr_branch="main" 2025-09-07T07:06:58.7864071Z curr_branch="main" 2025-09-07T07:06:58.7864299Z curr_ref_type="branch" 2025-09-07T07:06:58.7864554Z echo "Current branch is '$curr_branch'" 2025-09-07T07:06:58.7864806Z  2025-09-07T07:06:58.7864996Z python3 runner_determinator.py \ 2025-09-07T07:06:58.7865268Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T07:06:58.7865540Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T07:06:58.7865791Z  --github-branch "$curr_branch" \ 2025-09-07T07:06:58.7866076Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T07:06:58.7866360Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T07:06:58.7866636Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T07:06:58.7866907Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T07:06:58.7867207Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T07:06:58.7867573Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T07:06:58.7867863Z  --pr-number "${PR_NUMBER}" 2025-09-07T07:06:58.7898237Z shell: /usr/bin/bash -e {0} 2025-09-07T07:06:58.7898467Z env: 2025-09-07T07:06:58.7899008Z GITHUB_TOKEN: *** 2025-09-07T07:06:58.7899203Z ISSUE_NUMBER: 5132 2025-09-07T07:06:58.7899409Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:06:58.7899661Z ISSUE_OWNER: 2025-09-07T07:06:58.7899839Z CHECK_EXPERIMENTS: 2025-09-07T07:06:58.7900038Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:06:58.7900239Z PR_NUMBER: 2025-09-07T07:06:58.7900420Z ##[endgroup] 2025-09-07T07:06:58.7949005Z Current branch is 'main' 2025-09-07T07:07:00.3801192Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-09-07T07:07:00.3803653Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T07:07:00.3804593Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T07:07:00.3805510Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T07:07:00.3806232Z INFO : Setting output: label-type='' 2025-09-07T07:07:00.4113549Z Evaluate and set job outputs 2025-09-07T07:07:00.4120799Z Cleaning up orphan processes