2025-10-10T00:01:07.5808013Z Current runner version: '2.328.0' 2025-10-10T00:01:07.5842093Z ##[group]Runner Image Provisioner 2025-10-10T00:01:07.5843277Z Hosted Compute Agent 2025-10-10T00:01:07.5844073Z Version: 20250912.392 2025-10-10T00:01:07.5845214Z Commit: d921fda672a98b64f4f82364647e2f10b2267d0b 2025-10-10T00:01:07.5846250Z Build Date: 2025-09-12T15:23:14Z 2025-10-10T00:01:07.5847215Z ##[endgroup] 2025-10-10T00:01:07.5848266Z ##[group]Operating System 2025-10-10T00:01:07.5849478Z Ubuntu 2025-10-10T00:01:07.5850251Z 24.04.3 2025-10-10T00:01:07.5851046Z LTS 2025-10-10T00:01:07.5851810Z ##[endgroup] 2025-10-10T00:01:07.5852556Z ##[group]Runner Image 2025-10-10T00:01:07.5853726Z Image: ubuntu-24.04 2025-10-10T00:01:07.5854524Z Version: 20250929.60.1 2025-10-10T00:01:07.5856339Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250929.60/images/ubuntu/Ubuntu2404-Readme.md 2025-10-10T00:01:07.5859296Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250929.60 2025-10-10T00:01:07.5861581Z ##[endgroup] 2025-10-10T00:01:07.5863174Z ##[group]GITHUB_TOKEN Permissions 2025-10-10T00:01:07.5865995Z Contents: read 2025-10-10T00:01:07.5867331Z Metadata: read 2025-10-10T00:01:07.5868184Z ##[endgroup] 2025-10-10T00:01:07.5871629Z Secret source: Actions 2025-10-10T00:01:07.5873184Z Prepare workflow directory 2025-10-10T00:01:07.6615634Z Prepare all required actions 2025-10-10T00:01:07.6700071Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (344e6365a0068c2d2847fcec0c55dd53291d475e) 2025-10-10T00:01:07.6707816Z ##[group] Inputs 2025-10-10T00:01:07.6709007Z check_experiments: 2025-10-10T00:01:07.6709925Z opt_out_experiments: lf 2025-10-10T00:01:07.6710982Z triggering_actor: pytorchmergebot 2025-10-10T00:01:07.6711900Z issue_owner: 2025-10-10T00:01:07.6712733Z curr_branch: main 2025-10-10T00:01:07.6713640Z curr_ref_type: branch 2025-10-10T00:01:07.6714695Z issue_number: 5132 2025-10-10T00:01:07.6715492Z ##[endgroup] 2025-10-10T00:01:07.6716496Z Complete job name: get-label-type / runner-determinator 2025-10-10T00:01:08.1739599Z ##[group]Run cat < runner_determinator.py 2025-10-10T00:01:08.1742173Z cat < runner_determinator.py 2025-10-10T00:01:08.1742861Z # flake8: noqa: G004 2025-10-10T00:01:08.1743423Z  2025-10-10T00:01:08.1744302Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:01:08.1745370Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:01:08.1746358Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:01:08.1747142Z  2025-10-10T00:01:08.1747622Z """ 2025-10-10T00:01:08.1748686Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:01:08.1749911Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:01:08.1751169Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:01:08.1752185Z of which runners should be used to run which job. 2025-10-10T00:01:08.1752905Z  2025-10-10T00:01:08.1753608Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:01:08.1754726Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:01:08.1755815Z settings are considered to be empty with only the second part, the user 2025-10-10T00:01:08.1756668Z list, defined. 2025-10-10T00:01:08.1757240Z  2025-10-10T00:01:08.1757934Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:01:08.1759166Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:01:08.1760223Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:01:08.1760946Z  2025-10-10T00:01:08.1761966Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:01:08.1762964Z The user list is also a comma separated list of additional features or 2025-10-10T00:01:08.1763958Z experiments which the user could be opted in to. 2025-10-10T00:01:08.1764645Z  2025-10-10T00:01:08.1765162Z The user list has the following rules: 2025-10-10T00:01:08.1765874Z  2025-10-10T00:01:08.1766555Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:01:08.1767559Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:01:08.1852382Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:01:08.1853534Z  2025-10-10T00:01:08.1854294Z Example config: 2025-10-10T00:01:08.1855346Z  # A list of experiments that can be opted into. 2025-10-10T00:01:08.1856765Z  # This defines the behavior they'll induce when opted into. 2025-10-10T00:01:08.1857503Z  # Expected syntax is: 2025-10-10T00:01:08.1858258Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:01:08.1859633Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:01:08.1860437Z  2025-10-10T00:01:08.1860834Z  experiments: 2025-10-10T00:01:08.1861285Z  lf: 2025-10-10T00:01:08.1861722Z  rollout_percent: 25 2025-10-10T00:01:08.1862244Z  all_branches: false 2025-10-10T00:01:08.1862763Z  default: true 2025-10-10T00:01:08.1863243Z  --- 2025-10-10T00:01:08.1863632Z  2025-10-10T00:01:08.1864014Z  # Opt-ins: 2025-10-10T00:01:08.1864688Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:01:08.1865901Z  # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:01:08.1866784Z  # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:01:08.1867534Z  # Experiments should be from the above list. 2025-10-10T00:01:08.1868118Z  2025-10-10T00:01:08.1868717Z  @User1,-lf,split_build 2025-10-10T00:01:08.1869249Z  @User2,lf 2025-10-10T00:01:08.1869704Z  @User3,split_build 2025-10-10T00:01:08.1870185Z """ 2025-10-10T00:01:08.1870570Z  2025-10-10T00:01:08.1870954Z import json 2025-10-10T00:01:08.1871391Z import logging 2025-10-10T00:01:08.1871834Z import os 2025-10-10T00:01:08.1872259Z import random 2025-10-10T00:01:08.1872704Z import re 2025-10-10T00:01:08.1873121Z import sys 2025-10-10T00:01:08.1873586Z from argparse import ArgumentParser 2025-10-10T00:01:08.1874253Z from collections.abc import Iterable 2025-10-10T00:01:08.1874851Z from functools import cache 2025-10-10T00:01:08.1875389Z from logging import LogRecord 2025-10-10T00:01:08.1875955Z from typing import Any, NamedTuple 2025-10-10T00:01:08.1876560Z from urllib.request import Request, urlopen 2025-10-10T00:01:08.1877145Z  2025-10-10T00:01:08.1877526Z import yaml 2025-10-10T00:01:08.1877997Z from github import Auth, Github 2025-10-10T00:01:08.1878740Z from github.Issue import Issue 2025-10-10T00:01:08.1879270Z  2025-10-10T00:01:08.1879656Z  2025-10-10T00:01:08.1880118Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:01:08.1880892Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:01:08.1881859Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:01:08.1882632Z  2025-10-10T00:01:08.1883293Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:01:08.1883950Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:01:08.1884550Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:01:08.1885201Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:01:08.1885765Z  2025-10-10T00:01:08.1886195Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:01:08.1886749Z  2025-10-10T00:01:08.1887149Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:01:08.1887689Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:01:08.1888184Z  2025-10-10T00:01:08.1888746Z  2025-10-10T00:01:08.1889167Z class Experiment(NamedTuple): 2025-10-10T00:01:08.1889725Z  rollout_perc: float = ( 2025-10-10T00:01:08.1890472Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:01:08.1891200Z  ) 2025-10-10T00:01:08.1891627Z  all_branches: bool = ( 2025-10-10T00:01:08.1892355Z  False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:01:08.1893086Z  ) 2025-10-10T00:01:08.1893504Z  default: bool = ( 2025-10-10T00:01:08.1894169Z  True # If True, the experiment is enabled by default for all queries 2025-10-10T00:01:08.1894857Z  ) 2025-10-10T00:01:08.1895239Z  2025-10-10T00:01:08.1895653Z  # Add more fields as needed 2025-10-10T00:01:08.1896170Z  2025-10-10T00:01:08.1896536Z  2025-10-10T00:01:08.1896936Z class Settings(NamedTuple): 2025-10-10T00:01:08.1897449Z  """ 2025-10-10T00:01:08.1897980Z  Settings for the experiments that can be opted into. 2025-10-10T00:01:08.1898835Z  """ 2025-10-10T00:01:08.1899243Z  2025-10-10T00:01:08.1899702Z  experiments: dict[str, Experiment] = {} 2025-10-10T00:01:08.1900286Z  2025-10-10T00:01:08.1900802Z  2025-10-10T00:01:08.1901261Z class ColorFormatter(logging.Formatter): 2025-10-10T00:01:08.1901967Z  """Color codes the log messages based on the log level""" 2025-10-10T00:01:08.1902603Z  2025-10-10T00:01:08.1902983Z  COLORS = { 2025-10-10T00:01:08.1903464Z  "WARNING": "\033[33m", # Yellow 2025-10-10T00:01:08.1904031Z  "ERROR": "\033[31m", # Red 2025-10-10T00:01:08.1904583Z  "CRITICAL": "\033[31m", # Red 2025-10-10T00:01:08.1905144Z  "INFO": "\033[0m", # Reset 2025-10-10T00:01:08.1905697Z  "DEBUG": "\033[0m", # Reset 2025-10-10T00:01:08.1906231Z  } 2025-10-10T00:01:08.1906620Z  2025-10-10T00:01:08.1907073Z  def format(self, record: LogRecord) -> str: 2025-10-10T00:01:08.1907890Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:01:08.1909092Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:01:08.1909738Z  return super().format(record) 2025-10-10T00:01:08.1910271Z  2025-10-10T00:01:08.1910642Z  2025-10-10T00:01:08.1911066Z handler = logging.StreamHandler() 2025-10-10T00:01:08.1911873Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:01:08.1912638Z  2025-10-10T00:01:08.1913128Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:01:08.1913776Z log.addHandler(handler) 2025-10-10T00:01:08.1914298Z log.setLevel(logging.INFO) 2025-10-10T00:01:08.1914796Z  2025-10-10T00:01:08.1915152Z  2025-10-10T00:01:08.1915652Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:01:08.1916268Z  """ 2025-10-10T00:01:08.1916856Z  Defines outputs of the github action that invokes this script 2025-10-10T00:01:08.1917687Z  """ 2025-10-10T00:01:08.1918109Z  if not GITHUB_OUTPUT: 2025-10-10T00:01:08.1919487Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:01:08.1920683Z  log.warning( 2025-10-10T00:01:08.1921639Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:01:08.1922619Z  ) 2025-10-10T00:01:08.1923127Z  print(f"::set-output name={key}::{value}") 2025-10-10T00:01:08.1923726Z  return 2025-10-10T00:01:08.1924158Z  2025-10-10T00:01:08.1924591Z  with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:01:08.1925225Z  log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:01:08.1925851Z  f.write(f"{key}={value}\n") 2025-10-10T00:01:08.1926379Z  2025-10-10T00:01:08.1926758Z  2025-10-10T00:01:08.1927316Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:01:08.1928024Z  return frozenset( 2025-10-10T00:01:08.1928981Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:01:08.1929707Z  ) 2025-10-10T00:01:08.1930109Z  2025-10-10T00:01:08.1930483Z  2025-10-10T00:01:08.1930885Z def parse_args() -> Any: 2025-10-10T00:01:08.1931546Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:01:08.1932500Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:01:08.1933331Z  parser.add_argument( 2025-10-10T00:01:08.1933864Z  "--github-issue-repo", 2025-10-10T00:01:08.1934406Z  type=str, 2025-10-10T00:01:08.1934882Z  required=False, 2025-10-10T00:01:08.1935595Z  default="pytorch/test-infra", 2025-10-10T00:01:08.1936228Z  help="GitHub repo to get the issue", 2025-10-10T00:01:08.1936794Z  ) 2025-10-10T00:01:08.1937220Z  parser.add_argument( 2025-10-10T00:01:08.1937743Z  "--github-repo", 2025-10-10T00:01:08.1938247Z  type=str, 2025-10-10T00:01:08.1938969Z  required=True, 2025-10-10T00:01:08.1939549Z  help="GitHub repo where CI is running", 2025-10-10T00:01:08.1940156Z  ) 2025-10-10T00:01:08.1940586Z  parser.add_argument( 2025-10-10T00:01:08.1941292Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:01:08.1942005Z  ) 2025-10-10T00:01:08.1942432Z  parser.add_argument( 2025-10-10T00:01:08.1943150Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:01:08.1943891Z  ) 2025-10-10T00:01:08.1944307Z  parser.add_argument( 2025-10-10T00:01:08.1945044Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:01:08.1945779Z  ) 2025-10-10T00:01:08.1946207Z  parser.add_argument( 2025-10-10T00:01:08.1946971Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:01:08.1947725Z  ) 2025-10-10T00:01:08.1948168Z  parser.add_argument( 2025-10-10T00:01:08.1948889Z  "--github-ref-type", 2025-10-10T00:01:08.1949445Z  type=str, 2025-10-10T00:01:08.1949936Z  required=True, 2025-10-10T00:01:08.1950530Z  help="Current GitHub ref type, branch or tag", 2025-10-10T00:01:08.1951132Z  ) 2025-10-10T00:01:08.1951563Z  parser.add_argument( 2025-10-10T00:01:08.1952281Z  "--eligible-experiments", 2025-10-10T00:01:08.1952881Z  type=_str_comma_separated_to_set, 2025-10-10T00:01:08.1953453Z  required=False, 2025-10-10T00:01:08.1953955Z  default="", 2025-10-10T00:01:08.1954894Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:01:08.1955872Z  ) 2025-10-10T00:01:08.1956296Z  parser.add_argument( 2025-10-10T00:01:08.1956829Z  "--opt-out-experiments", 2025-10-10T00:01:08.1957417Z  type=_str_comma_separated_to_set, 2025-10-10T00:01:08.1957987Z  required=False, 2025-10-10T00:01:08.1958723Z  default="", 2025-10-10T00:01:08.1959212Z  help=( 2025-10-10T00:01:08.1959976Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:01:08.1961191Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:01:08.1962082Z  ), 2025-10-10T00:01:08.1962504Z  ) 2025-10-10T00:01:08.1962924Z  parser.add_argument( 2025-10-10T00:01:08.1963444Z  "--pr-number", 2025-10-10T00:01:08.1963939Z  type=str, 2025-10-10T00:01:08.1964427Z  required=False, 2025-10-10T00:01:08.1964925Z  default="", 2025-10-10T00:01:08.1965498Z  help="the optional PR number where this is run", 2025-10-10T00:01:08.1966110Z  ) 2025-10-10T00:01:08.1966505Z  2025-10-10T00:01:08.1966919Z  return parser.parse_args() 2025-10-10T00:01:08.1967440Z  2025-10-10T00:01:08.1967818Z  2025-10-10T00:01:08.1968637Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.1969643Z  auth = Auth.Token(github_token) 2025-10-10T00:01:08.1970245Z  return Github(auth=auth) 2025-10-10T00:01:08.1970755Z  2025-10-10T00:01:08.1971128Z  2025-10-10T00:01:08.1971840Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.1972711Z  repo = gh.get_repo(repo) 2025-10-10T00:01:08.1973293Z  return repo.get_issue(number=issue_num) 2025-10-10T00:01:08.1973869Z  2025-10-10T00:01:08.1974248Z  2025-10-10T00:01:08.1974649Z def get_potential_pr_author( 2025-10-10T00:01:08.1975395Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:01:08.1976120Z ) -> str: 2025-10-10T00:01:08.1976725Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:01:08.1977606Z  # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:01:08.1978676Z  # embedded in the tag name: ciflow// 2025-10-10T00:01:08.1979329Z  2025-10-10T00:01:08.1979761Z  gh = get_gh_client(github_token) 2025-10-10T00:01:08.1980303Z  2025-10-10T00:01:08.1980818Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:01:08.1981521Z  split_tag = ref_name.split("/") 2025-10-10T00:01:08.1982073Z  if ( 2025-10-10T00:01:08.1982533Z  len(split_tag) == 3 2025-10-10T00:01:08.1983104Z  and split_tag[0] == "ciflow" 2025-10-10T00:01:08.1983692Z  and split_tag[2].isnumeric() 2025-10-10T00:01:08.1984241Z  ): 2025-10-10T00:01:08.1984700Z  pr_number = split_tag[2] 2025-10-10T00:01:08.1985252Z  try: 2025-10-10T00:01:08.1985756Z  repository = gh.get_repo(repo) 2025-10-10T00:01:08.1986785Z  pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:01:08.1987461Z  except Exception as e: 2025-10-10T00:01:08.1988053Z  raise Exception( # noqa: TRY002 2025-10-10T00:01:08.1989526Z  f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:01:08.1990254Z  ) from e 2025-10-10T00:01:08.1990893Z  return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:01:08.1991666Z  # In all other cases, return the original input username 2025-10-10T00:01:08.1992318Z  return username 2025-10-10T00:01:08.1992784Z  2025-10-10T00:01:08.1993157Z  2025-10-10T00:01:08.1993628Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:01:08.1994219Z  """ 2025-10-10T00:01:08.1994946Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:01:08.1995789Z  """ 2025-10-10T00:01:08.1996419Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:01:08.1997143Z  2025-10-10T00:01:08.1997512Z  2025-10-10T00:01:08.1997942Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:01:08.1998604Z  try: 2025-10-10T00:01:08.1999068Z  data = yaml.safe_load(yaml_text) 2025-10-10T00:01:08.1999629Z  return data 2025-10-10T00:01:08.2000123Z  except yaml.YAMLError: 2025-10-10T00:01:08.2000690Z  log.exception("Error loading YAML") 2025-10-10T00:01:08.2001254Z  raise 2025-10-10T00:01:08.2001685Z  2025-10-10T00:01:08.2002054Z  2025-10-10T00:01:08.2002726Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:01:08.2003520Z  """ 2025-10-10T00:01:08.2004384Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:01:08.2005190Z  2025-10-10T00:01:08.2005783Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.2006641Z  and the text below is the list of opted in users. 2025-10-10T00:01:08.2007248Z  2025-10-10T00:01:08.2007882Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:01:08.2008742Z  """ 2025-10-10T00:01:08.2009255Z  rollout_state_parts = rollout_state.split("---") 2025-10-10T00:01:08.2009912Z  if len(rollout_state_parts) >= 2: 2025-10-10T00:01:08.2010792Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:01:08.2011481Z  else: 2025-10-10T00:01:08.2011922Z  return "", rollout_state 2025-10-10T00:01:08.2012438Z  2025-10-10T00:01:08.2012808Z  2025-10-10T00:01:08.2013251Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:01:08.2013805Z  """ 2025-10-10T00:01:08.2014396Z  Dictionary of users with a list of features they have opted into 2025-10-10T00:01:08.2015080Z  """ 2025-10-10T00:01:08.2015474Z  2025-10-10T00:01:08.2015833Z  2025-10-10T00:01:08.2016415Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:01:08.2017119Z  """ 2025-10-10T00:01:08.2017903Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:01:08.2018892Z  2025-10-10T00:01:08.2019765Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:01:08.2020826Z  - Example line: "@User1,lf,split_build" 2025-10-10T00:01:08.2021753Z  - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:01:08.2022426Z  2025-10-10T00:01:08.2022795Z  2025-10-10T00:01:08.2023156Z  """ 2025-10-10T00:01:08.2023580Z  optins = UserOptins() 2025-10-10T00:01:08.2024153Z  for user in user_optin_text.split("\n"): 2025-10-10T00:01:08.2024783Z  user = user.strip("\r\n\t -") 2025-10-10T00:01:08.2025390Z  if not user or not user.startswith("@"): 2025-10-10T00:01:08.2026003Z  # Not a valid user. Skip 2025-10-10T00:01:08.2026546Z  continue 2025-10-10T00:01:08.2027025Z  2025-10-10T00:01:08.2027412Z  if user: 2025-10-10T00:01:08.2027923Z  usr_name = user.split(",")[0].strip("@") 2025-10-10T00:01:08.2028940Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:01:08.2029636Z  2025-10-10T00:01:08.2030027Z  return optins 2025-10-10T00:01:08.2030474Z  2025-10-10T00:01:08.2030835Z  2025-10-10T00:01:08.2031379Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:01:08.2032035Z  """ 2025-10-10T00:01:08.2032504Z  Check if the experiment name is valid. 2025-10-10T00:01:08.2033068Z  A valid name: 2025-10-10T00:01:08.2033795Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:01:08.2034802Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:01:08.2035562Z  - Cannot contain spaces 2025-10-10T00:01:08.2036080Z  """ 2025-10-10T00:01:08.2036465Z  2025-10-10T00:01:08.2036961Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:01:08.2037735Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:01:08.2038740Z  2025-10-10T00:01:08.2039157Z  if valid: 2025-10-10T00:01:08.2039603Z  return True 2025-10-10T00:01:08.2040090Z  2025-10-10T00:01:08.2040468Z  log.error( 2025-10-10T00:01:08.2042019Z  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-10-10T00:01:08.2043623Z  ) 2025-10-10T00:01:08.2044030Z  return False 2025-10-10T00:01:08.2044474Z  2025-10-10T00:01:08.2044844Z  2025-10-10T00:01:08.2045405Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:01:08.2046086Z  """ 2025-10-10T00:01:08.2046742Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:01:08.2047497Z  """ 2025-10-10T00:01:08.2047898Z  try: 2025-10-10T00:01:08.2048312Z  if settings_text: 2025-10-10T00:01:08.2049231Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:01:08.2050111Z  # for easy reading 2025-10-10T00:01:08.2050991Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:01:08.2051938Z  # the backtick character in shell commands. 2025-10-10T00:01:08.2052600Z  backtick = chr(96) # backtick character 2025-10-10T00:01:08.2053341Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:01:08.2054070Z  settings = load_yaml(settings_text) 2025-10-10T00:01:08.2054617Z  2025-10-10T00:01:08.2055272Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:01:08.2056241Z  experiments = {} 2025-10-10T00:01:08.2056752Z  2025-10-10T00:01:08.2057350Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:01:08.2058180Z  if not is_valid_experiment_name(exp_name): 2025-10-10T00:01:08.2059481Z  # 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-10-10T00:01:08.2060593Z  continue 2025-10-10T00:01:08.2061085Z  2025-10-10T00:01:08.2061492Z  valid_settings = {} 2025-10-10T00:01:08.2062079Z  for setting in exp_settings: 2025-10-10T00:01:08.2062708Z  if setting not in Experiment._fields: 2025-10-10T00:01:08.2063313Z  log.warning( 2025-10-10T00:01:08.2064106Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:01:08.2064858Z  ) 2025-10-10T00:01:08.2065353Z  else: 2025-10-10T00:01:08.2065952Z  valid_settings[setting] = exp_settings[setting] 2025-10-10T00:01:08.2066565Z  2025-10-10T00:01:08.2067086Z  experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:01:08.2067776Z  return Settings(experiments) 2025-10-10T00:01:08.2068316Z  2025-10-10T00:01:08.2068807Z  except Exception: 2025-10-10T00:01:08.2069373Z  log.exception("Failed to parse settings") 2025-10-10T00:01:08.2069954Z  2025-10-10T00:01:08.2070349Z  return Settings() 2025-10-10T00:01:08.2070813Z  2025-10-10T00:01:08.2071178Z  2025-10-10T00:01:08.2071882Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:01:08.2072504Z  """ 2025-10-10T00:01:08.2073009Z  Parse settings, if any, from the rollout state. 2025-10-10T00:01:08.2073601Z  2025-10-10T00:01:08.2074191Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.2075004Z  and the text below is the list of opted in users. 2025-10-10T00:01:08.2075605Z  2025-10-10T00:01:08.2076254Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:01:08.2077027Z  """ 2025-10-10T00:01:08.2077653Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.2078580Z  return parse_settings_from_text(settings_text) 2025-10-10T00:01:08.2079171Z  2025-10-10T00:01:08.2079530Z  2025-10-10T00:01:08.2080043Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:01:08.2080656Z  """ 2025-10-10T00:01:08.2081103Z  Parse users from the rollout state. 2025-10-10T00:01:08.2081649Z  2025-10-10T00:01:08.2082006Z  """ 2025-10-10T00:01:08.2082611Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.2083419Z  return parse_user_opt_in_from_text(users_text) 2025-10-10T00:01:08.2084009Z  2025-10-10T00:01:08.2084372Z  2025-10-10T00:01:08.2085048Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.2085853Z  """ 2025-10-10T00:01:08.2086348Z  Check if a user is opted into an experiment 2025-10-10T00:01:08.2086938Z  """ 2025-10-10T00:01:08.2087465Z  return experiment_name in user_optins.get(user, []) 2025-10-10T00:01:08.2088092Z  2025-10-10T00:01:08.2088684Z  2025-10-10T00:01:08.2089370Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.2090188Z  """ 2025-10-10T00:01:08.2090716Z  Check if a user explicitly opted out of an experiment 2025-10-10T00:01:08.2091346Z  """ 2025-10-10T00:01:08.2091916Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:01:08.2092685Z  experiment_optout = "-" + experiment_name 2025-10-10T00:01:08.2093387Z  if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:01:08.2094039Z  return False 2025-10-10T00:01:08.2094498Z  2025-10-10T00:01:08.2095001Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:01:08.2095647Z  log.warning( 2025-10-10T00:01:08.2096540Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:01:08.2097468Z  ) 2025-10-10T00:01:08.2097865Z  2025-10-10T00:01:08.2098259Z  return True 2025-10-10T00:01:08.2098788Z  2025-10-10T00:01:08.2099161Z  2025-10-10T00:01:08.2099559Z def get_runner_prefix( 2025-10-10T00:01:08.2100066Z  rollout_state: str, 2025-10-10T00:01:08.2100619Z  workflow_requestors: Iterable[str], 2025-10-10T00:01:08.2101186Z  branch: str, 2025-10-10T00:01:08.2101763Z  eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.2102507Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.2103140Z  is_canary: bool = False, 2025-10-10T00:01:08.2103652Z ) -> str: 2025-10-10T00:01:08.2104142Z  settings = parse_settings(rollout_state) 2025-10-10T00:01:08.2104787Z  user_optins = parse_users(rollout_state) 2025-10-10T00:01:08.2105346Z  2025-10-10T00:01:08.2105871Z  fleet_prefix = "" 2025-10-10T00:01:08.2106363Z  prefixes = [] 2025-10-10T00:01:08.2107082Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:01:08.2108090Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:01:08.2108960Z  log.info( 2025-10-10T00:01:08.2109741Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:01:08.2110530Z  ) 2025-10-10T00:01:08.2110993Z  continue 2025-10-10T00:01:08.2111449Z  2025-10-10T00:01:08.2111857Z  if opt_out_experiments: 2025-10-10T00:01:08.2112469Z  if experiment_name in opt_out_experiments: 2025-10-10T00:01:08.2113178Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:01:08.2113832Z  log.info( 2025-10-10T00:01:08.2114834Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:01:08.2115851Z  ) 2025-10-10T00:01:08.2116313Z  continue 2025-10-10T00:01:08.2116801Z  2025-10-10T00:01:08.2117243Z  if eligible_experiments: 2025-10-10T00:01:08.2117880Z  if experiment_name not in eligible_experiments: 2025-10-10T00:01:08.2118674Z  exp_list = ", ".join(eligible_experiments) 2025-10-10T00:01:08.2119271Z  log.info( 2025-10-10T00:01:08.2120142Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:01:08.2121021Z  ) 2025-10-10T00:01:08.2121481Z  continue 2025-10-10T00:01:08.2122169Z  elif not experiment_settings.default: 2025-10-10T00:01:08.2122754Z  log.info( 2025-10-10T00:01:08.2123489Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:01:08.2124264Z  ) 2025-10-10T00:01:08.2124710Z  continue 2025-10-10T00:01:08.2125160Z  2025-10-10T00:01:08.2125674Z  # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:01:08.2126342Z  opted_out_users = [ 2025-10-10T00:01:08.2126857Z  requestor 2025-10-10T00:01:08.2127400Z  for requestor in workflow_requestors 2025-10-10T00:01:08.2128136Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.2128922Z  ] 2025-10-10T00:01:08.2129326Z  2025-10-10T00:01:08.2129729Z  if opted_out_users: 2025-10-10T00:01:08.2130273Z  log.info( 2025-10-10T00:01:08.2130979Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:01:08.2131724Z  ) 2025-10-10T00:01:08.2132166Z  continue 2025-10-10T00:01:08.2132620Z  2025-10-10T00:01:08.2133122Z  # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:01:08.2133781Z  opted_in_users = [ 2025-10-10T00:01:08.2134304Z  requestor 2025-10-10T00:01:08.2134856Z  for requestor in workflow_requestors 2025-10-10T00:01:08.2135590Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.2136255Z  ] 2025-10-10T00:01:08.2136662Z  2025-10-10T00:01:08.2137046Z  enabled = False 2025-10-10T00:01:08.2137555Z  if opted_in_users: 2025-10-10T00:01:08.2138199Z  log.info( 2025-10-10T00:01:08.2139006Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:01:08.2139735Z  ) 2025-10-10T00:01:08.2140211Z  enabled = True 2025-10-10T00:01:08.2140705Z  2025-10-10T00:01:08.2141152Z  elif experiment_settings.rollout_perc: 2025-10-10T00:01:08.2142051Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:01:08.2143060Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:01:08.2143767Z  log.info( 2025-10-10T00:01:08.2144728Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:01:08.2145693Z  ) 2025-10-10T00:01:08.2146177Z  enabled = True 2025-10-10T00:01:08.2146682Z  2025-10-10T00:01:08.2147075Z  if enabled: 2025-10-10T00:01:08.2147577Z  label = experiment_name 2025-10-10T00:01:08.2148195Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:01:08.2149180Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:01:08.2150117Z  # - If it's enabled, then we always list it's prefix first 2025-10-10T00:01:08.2150940Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:01:08.2151647Z  if is_canary: 2025-10-10T00:01:08.2152216Z  label += CANARY_FLEET_SUFFIX 2025-10-10T00:01:08.2152818Z  fleet_prefix = label 2025-10-10T00:01:08.2153359Z  else: 2025-10-10T00:01:08.2153993Z  prefixes.append(label) 2025-10-10T00:01:08.2154529Z  2025-10-10T00:01:08.2154932Z  if len(prefixes) > 1: 2025-10-10T00:01:08.2155434Z  log.error( 2025-10-10T00:01:08.2156567Z  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-10-10T00:01:08.2157742Z  ) 2025-10-10T00:01:08.2158185Z  prefixes = prefixes[:1] 2025-10-10T00:01:08.2158806Z  2025-10-10T00:01:08.2159204Z  # Fleet always comes first 2025-10-10T00:01:08.2159746Z  if fleet_prefix: 2025-10-10T00:01:08.2160267Z  prefixes.insert(0, fleet_prefix) 2025-10-10T00:01:08.2160817Z  2025-10-10T00:01:08.2161315Z  return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:01:08.2161923Z  2025-10-10T00:01:08.2162301Z  2025-10-10T00:01:08.2162989Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:01:08.2163808Z  """ 2025-10-10T00:01:08.2164459Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:01:08.2165208Z  2025-10-10T00:01:08.2165834Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:01:08.2166577Z  """ 2025-10-10T00:01:08.2167038Z  gh = get_gh_client(github_token) 2025-10-10T00:01:08.2167643Z  issue = get_issue(gh, repo, issue_num) 2025-10-10T00:01:08.2168347Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:01:08.2169073Z  2025-10-10T00:01:08.2169436Z  2025-10-10T00:01:08.2170073Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:01:08.2171024Z  for _ in range(num_retries): 2025-10-10T00:01:08.2171557Z  try: 2025-10-10T00:01:08.2172048Z  req = Request(url=url, headers=headers) 2025-10-10T00:01:08.2172765Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:01:08.2173459Z  return json.loads(content) 2025-10-10T00:01:08.2174036Z  except Exception as e: 2025-10-10T00:01:08.2174647Z  log.warning(f"Could not download {url}: {e}") 2025-10-10T00:01:08.2175234Z  2025-10-10T00:01:08.2175855Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:01:08.2176614Z  return {} 2025-10-10T00:01:08.2177050Z  2025-10-10T00:01:08.2177415Z  2025-10-10T00:01:08.2177787Z @cache 2025-10-10T00:01:08.2178659Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:01:08.2179480Z  """ 2025-10-10T00:01:08.2179935Z  Dynamically get PR information 2025-10-10T00:01:08.2180465Z  """ 2025-10-10T00:01:08.2181106Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:01:08.2181783Z  headers = { 2025-10-10T00:01:08.2182317Z  "Accept": "application/vnd.github.v3+json", 2025-10-10T00:01:08.2182977Z  "Authorization": f"token {github_token}", 2025-10-10T00:01:08.2183552Z  } 2025-10-10T00:01:08.2184041Z  json_response: dict[str, Any] = download_json( 2025-10-10T00:01:08.2184711Z  url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:01:08.2185306Z  headers=headers, 2025-10-10T00:01:08.2185788Z  ) 2025-10-10T00:01:08.2186184Z  2025-10-10T00:01:08.2186587Z  if not json_response: 2025-10-10T00:01:08.2187244Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:01:08.2188060Z  return {} 2025-10-10T00:01:08.2188624Z  2025-10-10T00:01:08.2189018Z  return json_response 2025-10-10T00:01:08.2189502Z  2025-10-10T00:01:08.2189870Z  2025-10-10T00:01:08.2190514Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:01:08.2191294Z  """ 2025-10-10T00:01:08.2191883Z  Dynamically get the latest list of labels from the pull request 2025-10-10T00:01:08.2192585Z  """ 2025-10-10T00:01:08.2193135Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:01:08.2193796Z  return { 2025-10-10T00:01:08.2194456Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:01:08.2195183Z  } 2025-10-10T00:01:08.2195576Z  2025-10-10T00:01:08.2195939Z  2025-10-10T00:01:08.2196343Z def main() -> None: 2025-10-10T00:01:08.2196824Z  args = parse_args() 2025-10-10T00:01:08.2197308Z  2025-10-10T00:01:08.2197765Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:01:08.2198341Z  2025-10-10T00:01:08.2198843Z  # Check if the PR is opt-out 2025-10-10T00:01:08.2199388Z  if args.pr_number: 2025-10-10T00:01:08.2200130Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:01:08.2200932Z  if OPT_OUT_LABEL in labels: 2025-10-10T00:01:08.2201484Z  log.info( 2025-10-10T00:01:08.2202248Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:01:08.2203051Z  ) 2025-10-10T00:01:08.2203679Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.2204413Z  sys.exit() 2025-10-10T00:01:08.2205019Z  2025-10-10T00:01:08.2205397Z  try: 2025-10-10T00:01:08.2205903Z  rollout_state = get_rollout_state_from_issue( 2025-10-10T00:01:08.2206682Z  args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:01:08.2207366Z  ) 2025-10-10T00:01:08.2207774Z  2025-10-10T00:01:08.2208206Z  username = get_potential_pr_author( 2025-10-10T00:01:08.2208932Z  args.github_token, 2025-10-10T00:01:08.2209469Z  args.github_repo, 2025-10-10T00:01:08.2210017Z  args.github_actor, 2025-10-10T00:01:08.2210567Z  args.github_ref_type, 2025-10-10T00:01:08.2211117Z  args.github_branch, 2025-10-10T00:01:08.2211626Z  ) 2025-10-10T00:01:08.2212032Z  2025-10-10T00:01:08.2212562Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:01:08.2213217Z  2025-10-10T00:01:08.2213675Z  runner_label_prefix = get_runner_prefix( 2025-10-10T00:01:08.2214268Z  rollout_state, 2025-10-10T00:01:08.2214838Z  (args.github_issue_owner, username), 2025-10-10T00:01:08.2215431Z  args.github_branch, 2025-10-10T00:01:08.2216006Z  args.eligible_experiments, 2025-10-10T00:01:08.2216598Z  args.opt_out_experiments, 2025-10-10T00:01:08.2217152Z  is_canary, 2025-10-10T00:01:08.2217625Z  ) 2025-10-10T00:01:08.2218030Z  2025-10-10T00:01:08.2218534Z  except Exception as e: 2025-10-10T00:01:08.2219054Z  log.error( 2025-10-10T00:01:08.2219821Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:01:08.2220613Z  ) 2025-10-10T00:01:08.2221160Z  2025-10-10T00:01:08.2221740Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.2222432Z  2025-10-10T00:01:08.2222807Z  2025-10-10T00:01:08.2223195Z if __name__ == "__main__": 2025-10-10T00:01:08.2223696Z  main() 2025-10-10T00:01:08.2224103Z  2025-10-10T00:01:08.2224474Z EOF 2025-10-10T00:01:08.2224844Z  2025-10-10T00:01:08.2225249Z cat runner_determinator.py 2025-10-10T00:01:08.2580431Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:08.2581449Z env: 2025-10-10T00:01:08.2582169Z GITHUB_TOKEN: *** 2025-10-10T00:01:08.2582587Z ISSUE_NUMBER: 5132 2025-10-10T00:01:08.2583032Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:08.2583533Z ISSUE_OWNER: 2025-10-10T00:01:08.2583928Z CHECK_EXPERIMENTS: 2025-10-10T00:01:08.2584371Z OPT_OUT_EXPERIMENTS: lf 2025-10-10T00:01:08.2584813Z PR_NUMBER: 2025-10-10T00:01:08.2585215Z ##[endgroup] 2025-10-10T00:01:08.2792726Z # flake8: noqa: G004 2025-10-10T00:01:08.2793078Z 2025-10-10T00:01:08.2793503Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:01:08.2794454Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:01:08.2795253Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:01:08.2795693Z 2025-10-10T00:01:08.2795851Z """ 2025-10-10T00:01:08.2796421Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:01:08.2797282Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:01:08.2798169Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:01:08.2799252Z of which runners should be used to run which job. 2025-10-10T00:01:08.2799649Z 2025-10-10T00:01:08.2800037Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:01:08.2801119Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:01:08.2802001Z settings are considered to be empty with only the second part, the user 2025-10-10T00:01:08.2802675Z list, defined. 2025-10-10T00:01:08.2802902Z 2025-10-10T00:01:08.2803261Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:01:08.2804287Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:01:08.2805099Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:01:08.2805532Z 2025-10-10T00:01:08.2805893Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:01:08.2806736Z The user list is also a comma separated list of additional features or 2025-10-10T00:01:08.2807456Z experiments which the user could be opted in to. 2025-10-10T00:01:08.2807849Z 2025-10-10T00:01:08.2808048Z The user list has the following rules: 2025-10-10T00:01:08.2808594Z 2025-10-10T00:01:08.2809075Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:01:08.2809965Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:01:08.2810719Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:01:08.2811111Z 2025-10-10T00:01:08.2811287Z Example config: 2025-10-10T00:01:08.2811730Z # A list of experiments that can be opted into. 2025-10-10T00:01:08.2812387Z # This defines the behavior they'll induce when opted into. 2025-10-10T00:01:08.2812994Z # Expected syntax is: 2025-10-10T00:01:08.2813627Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:01:08.2814592Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:01:08.2815205Z 2025-10-10T00:01:08.2815373Z experiments: 2025-10-10T00:01:08.2815763Z lf: 2025-10-10T00:01:08.2816130Z rollout_percent: 25 2025-10-10T00:01:08.2816750Z all_branches: false 2025-10-10T00:01:08.2817190Z default: true 2025-10-10T00:01:08.2817596Z --- 2025-10-10T00:01:08.2817796Z 2025-10-10T00:01:08.2817953Z # Opt-ins: 2025-10-10T00:01:08.2818712Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:01:08.2819580Z # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:01:08.2820352Z # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:01:08.2820994Z # Experiments should be from the above list. 2025-10-10T00:01:08.2821366Z 2025-10-10T00:01:08.2821541Z @User1,-lf,split_build 2025-10-10T00:01:08.2821971Z @User2,lf 2025-10-10T00:01:08.2822382Z @User3,split_build 2025-10-10T00:01:08.2822782Z """ 2025-10-10T00:01:08.2822969Z 2025-10-10T00:01:08.2823135Z import json 2025-10-10T00:01:08.2823496Z import logging 2025-10-10T00:01:08.2823870Z import os 2025-10-10T00:01:08.2824233Z import random 2025-10-10T00:01:08.2824606Z import re 2025-10-10T00:01:08.2824972Z import sys 2025-10-10T00:01:08.2825365Z from argparse import ArgumentParser 2025-10-10T00:01:08.2825896Z from collections.abc import Iterable 2025-10-10T00:01:08.2826405Z from functools import cache 2025-10-10T00:01:08.2826864Z from logging import LogRecord 2025-10-10T00:01:08.2827346Z from typing import Any, NamedTuple 2025-10-10T00:01:08.2827861Z from urllib.request import Request, urlopen 2025-10-10T00:01:08.2828230Z 2025-10-10T00:01:08.2828558Z import yaml 2025-10-10T00:01:08.2828971Z from github import Auth, Github 2025-10-10T00:01:08.2829462Z from github.Issue import Issue 2025-10-10T00:01:08.2829758Z 2025-10-10T00:01:08.2829765Z 2025-10-10T00:01:08.2829984Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:01:08.2830657Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:01:08.2831514Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:01:08.2832063Z 2025-10-10T00:01:08.2832288Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:01:08.2833005Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:01:08.2833513Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:01:08.2834057Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:01:08.2834407Z 2025-10-10T00:01:08.2834603Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:01:08.2834929Z 2025-10-10T00:01:08.2835118Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:01:08.2835573Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:01:08.2835851Z 2025-10-10T00:01:08.2835857Z 2025-10-10T00:01:08.2836041Z class Experiment(NamedTuple): 2025-10-10T00:01:08.2836514Z rollout_perc: float = ( 2025-10-10T00:01:08.2837133Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:01:08.2837803Z ) 2025-10-10T00:01:08.2838168Z all_branches: bool = ( 2025-10-10T00:01:08.2839025Z False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:01:08.2839729Z ) 2025-10-10T00:01:08.2840087Z default: bool = ( 2025-10-10T00:01:08.2840653Z True # If True, the experiment is enabled by default for all queries 2025-10-10T00:01:08.2841287Z ) 2025-10-10T00:01:08.2841482Z 2025-10-10T00:01:08.2841658Z # Add more fields as needed 2025-10-10T00:01:08.2841950Z 2025-10-10T00:01:08.2841957Z 2025-10-10T00:01:08.2842143Z class Settings(NamedTuple): 2025-10-10T00:01:08.2842574Z """ 2025-10-10T00:01:08.2843019Z Settings for the experiments that can be opted into. 2025-10-10T00:01:08.2843575Z """ 2025-10-10T00:01:08.2843769Z 2025-10-10T00:01:08.2843975Z experiments: dict[str, Experiment] = {} 2025-10-10T00:01:08.2844327Z 2025-10-10T00:01:08.2844334Z 2025-10-10T00:01:08.2844538Z class ColorFormatter(logging.Formatter): 2025-10-10T00:01:08.2845151Z """Color codes the log messages based on the log level""" 2025-10-10T00:01:08.2845579Z 2025-10-10T00:01:08.2845740Z COLORS = { 2025-10-10T00:01:08.2846126Z "WARNING": "\033[33m", # Yellow 2025-10-10T00:01:08.2846774Z "ERROR": "\033[31m", # Red 2025-10-10T00:01:08.2847253Z "CRITICAL": "\033[31m", # Red 2025-10-10T00:01:08.2847738Z "INFO": "\033[0m", # Reset 2025-10-10T00:01:08.2848203Z "DEBUG": "\033[0m", # Reset 2025-10-10T00:01:08.2848891Z } 2025-10-10T00:01:08.2849087Z 2025-10-10T00:01:08.2849312Z def format(self, record: LogRecord) -> str: 2025-10-10T00:01:08.2850052Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:01:08.2850814Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:01:08.2851397Z return super().format(record) 2025-10-10T00:01:08.2851725Z 2025-10-10T00:01:08.2851732Z 2025-10-10T00:01:08.2851931Z handler = logging.StreamHandler() 2025-10-10T00:01:08.2852625Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:01:08.2853180Z 2025-10-10T00:01:08.2853419Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:01:08.2853983Z log.addHandler(handler) 2025-10-10T00:01:08.2854428Z log.setLevel(logging.INFO) 2025-10-10T00:01:08.2854709Z 2025-10-10T00:01:08.2854716Z 2025-10-10T00:01:08.2854961Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:01:08.2855510Z """ 2025-10-10T00:01:08.2856198Z Defines outputs of the github action that invokes this script 2025-10-10T00:01:08.2856811Z """ 2025-10-10T00:01:08.2857169Z if not GITHUB_OUTPUT: 2025-10-10T00:01:08.2858232Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:01:08.2859567Z log.warning( 2025-10-10T00:01:08.2860425Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:01:08.2861342Z ) 2025-10-10T00:01:08.2871406Z print(f"::set-output name={key}::{value}") 2025-10-10T00:01:08.2872009Z return 2025-10-10T00:01:08.2872246Z 2025-10-10T00:01:08.2872653Z with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:01:08.2873279Z log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:01:08.2873858Z f.write(f"{key}={value}\n") 2025-10-10T00:01:08.2874179Z 2025-10-10T00:01:08.2874187Z 2025-10-10T00:01:08.2874496Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:01:08.2875123Z return frozenset( 2025-10-10T00:01:08.2875735Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:01:08.2876417Z ) 2025-10-10T00:01:08.2876618Z 2025-10-10T00:01:08.2876625Z 2025-10-10T00:01:08.2876802Z def parse_args() -> Any: 2025-10-10T00:01:08.2877343Z parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:01:08.2878199Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:01:08.2879239Z parser.add_argument( 2025-10-10T00:01:08.2879702Z "--github-issue-repo", 2025-10-10T00:01:08.2880174Z type=str, 2025-10-10T00:01:08.2880576Z required=False, 2025-10-10T00:01:08.2881028Z default="pytorch/test-infra", 2025-10-10T00:01:08.2881558Z help="GitHub repo to get the issue", 2025-10-10T00:01:08.2882060Z ) 2025-10-10T00:01:08.2882419Z parser.add_argument( 2025-10-10T00:01:08.2882862Z "--github-repo", 2025-10-10T00:01:08.2883283Z type=str, 2025-10-10T00:01:08.2883674Z required=True, 2025-10-10T00:01:08.2884137Z help="GitHub repo where CI is running", 2025-10-10T00:01:08.2884669Z ) 2025-10-10T00:01:08.2885041Z parser.add_argument( 2025-10-10T00:01:08.2885649Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:01:08.2886303Z ) 2025-10-10T00:01:08.2886668Z parser.add_argument( 2025-10-10T00:01:08.2887286Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:01:08.2887954Z ) 2025-10-10T00:01:08.2888318Z parser.add_argument( 2025-10-10T00:01:08.2889325Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:01:08.2890035Z ) 2025-10-10T00:01:08.2890421Z parser.add_argument( 2025-10-10T00:01:08.2941508Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:01:08.2942325Z ) 2025-10-10T00:01:08.2942702Z parser.add_argument( 2025-10-10T00:01:08.2943157Z "--github-ref-type", 2025-10-10T00:01:08.2943610Z type=str, 2025-10-10T00:01:08.2944011Z required=True, 2025-10-10T00:01:08.2944495Z help="Current GitHub ref type, branch or tag", 2025-10-10T00:01:08.2945042Z ) 2025-10-10T00:01:08.2945405Z parser.add_argument( 2025-10-10T00:01:08.2945863Z "--eligible-experiments", 2025-10-10T00:01:08.2946359Z type=_str_comma_separated_to_set, 2025-10-10T00:01:08.2946865Z required=False, 2025-10-10T00:01:08.2947268Z default="", 2025-10-10T00:01:08.2948116Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:01:08.2949194Z ) 2025-10-10T00:01:08.2949553Z parser.add_argument( 2025-10-10T00:01:08.2949999Z "--opt-out-experiments", 2025-10-10T00:01:08.2950482Z type=_str_comma_separated_to_set, 2025-10-10T00:01:08.2950988Z required=False, 2025-10-10T00:01:08.2951390Z default="", 2025-10-10T00:01:08.2951772Z help=( 2025-10-10T00:01:08.2952418Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:01:08.2953523Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:01:08.2954343Z ), 2025-10-10T00:01:08.2954683Z ) 2025-10-10T00:01:08.2955043Z parser.add_argument( 2025-10-10T00:01:08.2955466Z "--pr-number", 2025-10-10T00:01:08.2955870Z type=str, 2025-10-10T00:01:08.2956254Z required=False, 2025-10-10T00:01:08.2956674Z default="", 2025-10-10T00:01:08.2957304Z help="the optional PR number where this is run", 2025-10-10T00:01:08.2957871Z ) 2025-10-10T00:01:08.2958066Z 2025-10-10T00:01:08.2958255Z return parser.parse_args() 2025-10-10T00:01:08.2958673Z 2025-10-10T00:01:08.2958680Z 2025-10-10T00:01:08.2959078Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.2959829Z auth = Auth.Token(github_token) 2025-10-10T00:01:08.2960316Z return Github(auth=auth) 2025-10-10T00:01:08.2960613Z 2025-10-10T00:01:08.2960619Z 2025-10-10T00:01:08.2961067Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.2961848Z repo = gh.get_repo(repo) 2025-10-10T00:01:08.2962341Z return repo.get_issue(number=issue_num) 2025-10-10T00:01:08.2962692Z 2025-10-10T00:01:08.2962698Z 2025-10-10T00:01:08.2962886Z def get_potential_pr_author( 2025-10-10T00:01:08.2963522Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:01:08.2964191Z ) -> str: 2025-10-10T00:01:08.2964691Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:01:08.2965478Z # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:01:08.2966194Z # embedded in the tag name: ciflow// 2025-10-10T00:01:08.2966605Z 2025-10-10T00:01:08.2966788Z gh = get_gh_client(github_token) 2025-10-10T00:01:08.2967112Z 2025-10-10T00:01:08.2967378Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:01:08.2967980Z split_tag = ref_name.split("/") 2025-10-10T00:01:08.2968576Z if ( 2025-10-10T00:01:08.2968954Z len(split_tag) == 3 2025-10-10T00:01:08.2969426Z and split_tag[0] == "ciflow" 2025-10-10T00:01:08.2969941Z and split_tag[2].isnumeric() 2025-10-10T00:01:08.2970426Z ): 2025-10-10T00:01:08.2970796Z pr_number = split_tag[2] 2025-10-10T00:01:08.2971419Z try: 2025-10-10T00:01:08.2971834Z repository = gh.get_repo(repo) 2025-10-10T00:01:08.2972423Z pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:01:08.2973008Z except Exception as e: 2025-10-10T00:01:08.2973505Z raise Exception( # noqa: TRY002 2025-10-10T00:01:08.2974159Z f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:01:08.2974778Z ) from e 2025-10-10T00:01:08.2975302Z return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:01:08.2975979Z # In all other cases, return the original input username 2025-10-10T00:01:08.2976547Z return username 2025-10-10T00:01:08.2976784Z 2025-10-10T00:01:08.2976790Z 2025-10-10T00:01:08.2977018Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:01:08.2977539Z """ 2025-10-10T00:01:08.2978165Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:01:08.2979040Z """ 2025-10-10T00:01:08.2979577Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:01:08.2980086Z 2025-10-10T00:01:08.2980093Z 2025-10-10T00:01:08.2980287Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:01:08.2980762Z try: 2025-10-10T00:01:08.2981140Z data = yaml.safe_load(yaml_text) 2025-10-10T00:01:08.2981633Z return data 2025-10-10T00:01:08.2982036Z except yaml.YAMLError: 2025-10-10T00:01:08.2982496Z log.exception("Error loading YAML") 2025-10-10T00:01:08.2982996Z raise 2025-10-10T00:01:08.2983205Z 2025-10-10T00:01:08.2983213Z 2025-10-10T00:01:08.2983614Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:01:08.2984339Z """ 2025-10-10T00:01:08.2984943Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:01:08.2985530Z 2025-10-10T00:01:08.2985997Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.2986743Z and the text below is the list of opted in users. 2025-10-10T00:01:08.2987136Z 2025-10-10T00:01:08.2987497Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:01:08.2988183Z """ 2025-10-10T00:01:08.2988722Z rollout_state_parts = rollout_state.split("---") 2025-10-10T00:01:08.2989325Z if len(rollout_state_parts) >= 2: 2025-10-10T00:01:08.2989912Z return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:01:08.2990481Z else: 2025-10-10T00:01:08.2990848Z return "", rollout_state 2025-10-10T00:01:08.2991151Z 2025-10-10T00:01:08.2991158Z 2025-10-10T00:01:08.2991354Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:01:08.2991857Z """ 2025-10-10T00:01:08.2992355Z Dictionary of users with a list of features they have opted into 2025-10-10T00:01:08.2992989Z """ 2025-10-10T00:01:08.2993177Z 2025-10-10T00:01:08.2993191Z 2025-10-10T00:01:08.2993523Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:01:08.2994154Z """ 2025-10-10T00:01:08.2994844Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:01:08.2995512Z 2025-10-10T00:01:08.2996116Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:01:08.2997094Z - Example line: "@User1,lf,split_build" 2025-10-10T00:01:08.2997758Z - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:01:08.2998232Z 2025-10-10T00:01:08.2998239Z 2025-10-10T00:01:08.2998850Z """ 2025-10-10T00:01:08.2999294Z optins = UserOptins() 2025-10-10T00:01:08.2999770Z for user in user_optin_text.split("\n"): 2025-10-10T00:01:08.3000313Z user = user.strip("\r\n\t -") 2025-10-10T00:01:08.3000836Z if not user or not user.startswith("@"): 2025-10-10T00:01:08.3001574Z # Not a valid user. Skip 2025-10-10T00:01:08.3002043Z continue 2025-10-10T00:01:08.3002285Z 2025-10-10T00:01:08.3002437Z if user: 2025-10-10T00:01:08.3002855Z usr_name = user.split(",")[0].strip("@") 2025-10-10T00:01:08.3003528Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:01:08.3004013Z 2025-10-10T00:01:08.3004178Z return optins 2025-10-10T00:01:08.3004408Z 2025-10-10T00:01:08.3004415Z 2025-10-10T00:01:08.3004691Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:01:08.3005275Z """ 2025-10-10T00:01:08.3005652Z Check if the experiment name is valid. 2025-10-10T00:01:08.3006157Z A valid name: 2025-10-10T00:01:08.3006776Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:01:08.3007687Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:01:08.3008530Z - Cannot contain spaces 2025-10-10T00:01:08.3008985Z """ 2025-10-10T00:01:08.3009183Z 2025-10-10T00:01:08.3009435Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:01:08.3010115Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:01:08.3010559Z 2025-10-10T00:01:08.3010715Z if valid: 2025-10-10T00:01:08.3011081Z return True 2025-10-10T00:01:08.3011312Z 2025-10-10T00:01:08.3011476Z log.error( 2025-10-10T00:01:08.3012907Z 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-10-10T00:01:08.3014439Z ) 2025-10-10T00:01:08.3014782Z return False 2025-10-10T00:01:08.3015010Z 2025-10-10T00:01:08.3015016Z 2025-10-10T00:01:08.3015315Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:01:08.3015917Z """ 2025-10-10T00:01:08.3016676Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:01:08.3017402Z """ 2025-10-10T00:01:08.3017747Z try: 2025-10-10T00:01:08.3018102Z if settings_text: 2025-10-10T00:01:08.3018932Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:01:08.3019710Z # for easy reading 2025-10-10T00:01:08.3020483Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:01:08.3021356Z # the backtick character in shell commands. 2025-10-10T00:01:08.3021939Z backtick = chr(96) # backtick character 2025-10-10T00:01:08.3022586Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:01:08.3023228Z settings = load_yaml(settings_text) 2025-10-10T00:01:08.3023596Z 2025-10-10T00:01:08.3024007Z # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:01:08.3024761Z experiments = {} 2025-10-10T00:01:08.3025048Z 2025-10-10T00:01:08.3025431Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:01:08.3026176Z if not is_valid_experiment_name(exp_name): 2025-10-10T00:01:08.3027272Z # 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-10-10T00:01:08.3028305Z continue 2025-10-10T00:01:08.3028684Z 2025-10-10T00:01:08.3028862Z valid_settings = {} 2025-10-10T00:01:08.3029371Z for setting in exp_settings: 2025-10-10T00:01:08.3029926Z if setting not in Experiment._fields: 2025-10-10T00:01:08.3030625Z log.warning( 2025-10-10T00:01:08.3031994Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:01:08.3033058Z ) 2025-10-10T00:01:08.3033491Z else: 2025-10-10T00:01:08.3033995Z valid_settings[setting] = exp_settings[setting] 2025-10-10T00:01:08.3034417Z 2025-10-10T00:01:08.3034712Z experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:01:08.3035339Z return Settings(experiments) 2025-10-10T00:01:08.3035683Z 2025-10-10T00:01:08.3035855Z except Exception: 2025-10-10T00:01:08.3036327Z log.exception("Failed to parse settings") 2025-10-10T00:01:08.3036705Z 2025-10-10T00:01:08.3036871Z return Settings() 2025-10-10T00:01:08.3037128Z 2025-10-10T00:01:08.3037135Z 2025-10-10T00:01:08.3037377Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:01:08.3037923Z """ 2025-10-10T00:01:08.3038346Z Parse settings, if any, from the rollout state. 2025-10-10T00:01:08.3038918Z 2025-10-10T00:01:08.3039302Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.3040061Z and the text below is the list of opted in users. 2025-10-10T00:01:08.3040701Z 2025-10-10T00:01:08.3041113Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:01:08.3041834Z """ 2025-10-10T00:01:08.3042385Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.3043128Z return parse_settings_from_text(settings_text) 2025-10-10T00:01:08.3043526Z 2025-10-10T00:01:08.3043534Z 2025-10-10T00:01:08.3043772Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:01:08.3044406Z """ 2025-10-10T00:01:08.3044802Z Parse users from the rollout state. 2025-10-10T00:01:08.3045279Z 2025-10-10T00:01:08.3045437Z """ 2025-10-10T00:01:08.3046028Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.3046771Z return parse_user_opt_in_from_text(users_text) 2025-10-10T00:01:08.3047177Z 2025-10-10T00:01:08.3047184Z 2025-10-10T00:01:08.3047745Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.3048623Z """ 2025-10-10T00:01:08.3049039Z Check if a user is opted into an experiment 2025-10-10T00:01:08.3049605Z """ 2025-10-10T00:01:08.3050046Z return experiment_name in user_optins.get(user, []) 2025-10-10T00:01:08.3050465Z 2025-10-10T00:01:08.3050471Z 2025-10-10T00:01:08.3050885Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.3051620Z """ 2025-10-10T00:01:08.3052080Z Check if a user explicitly opted out of an experiment 2025-10-10T00:01:08.3052650Z """ 2025-10-10T00:01:08.3053207Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:01:08.3054091Z experiment_optout = "-" + experiment_name 2025-10-10T00:01:08.3054953Z if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:01:08.3055547Z return False 2025-10-10T00:01:08.3055812Z 2025-10-10T00:01:08.3056085Z if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:01:08.3056681Z log.warning( 2025-10-10T00:01:08.3057470Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:01:08.3058342Z ) 2025-10-10T00:01:08.3058676Z 2025-10-10T00:01:08.3058837Z return True 2025-10-10T00:01:08.3059073Z 2025-10-10T00:01:08.3059080Z 2025-10-10T00:01:08.3059252Z def get_runner_prefix( 2025-10-10T00:01:08.3059677Z rollout_state: str, 2025-10-10T00:01:08.3060133Z workflow_requestors: Iterable[str], 2025-10-10T00:01:08.3060639Z branch: str, 2025-10-10T00:01:08.3061119Z eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.3061771Z opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.3062351Z is_canary: bool = False, 2025-10-10T00:01:08.3062808Z ) -> str: 2025-10-10T00:01:08.3063213Z settings = parse_settings(rollout_state) 2025-10-10T00:01:08.3063981Z user_optins = parse_users(rollout_state) 2025-10-10T00:01:08.3064349Z 2025-10-10T00:01:08.3064520Z fleet_prefix = "" 2025-10-10T00:01:08.3064938Z prefixes = [] 2025-10-10T00:01:08.3065554Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:01:08.3066477Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:01:08.3067177Z log.info( 2025-10-10T00:01:08.3067841Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:01:08.3068706Z ) 2025-10-10T00:01:08.3069076Z continue 2025-10-10T00:01:08.3069324Z 2025-10-10T00:01:08.3069508Z if opt_out_experiments: 2025-10-10T00:01:08.3070036Z if experiment_name in opt_out_experiments: 2025-10-10T00:01:08.3070670Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:01:08.3071273Z log.info( 2025-10-10T00:01:08.3072195Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:01:08.3073151Z ) 2025-10-10T00:01:08.3073536Z continue 2025-10-10T00:01:08.3073804Z 2025-10-10T00:01:08.3073988Z if eligible_experiments: 2025-10-10T00:01:08.3074558Z if experiment_name not in eligible_experiments: 2025-10-10T00:01:08.3075187Z exp_list = ", ".join(eligible_experiments) 2025-10-10T00:01:08.3075737Z log.info( 2025-10-10T00:01:08.3076498Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:01:08.3077318Z ) 2025-10-10T00:01:08.3077701Z continue 2025-10-10T00:01:08.3078164Z elif not experiment_settings.default: 2025-10-10T00:01:08.3078852Z log.info( 2025-10-10T00:01:08.3079640Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:01:08.3080372Z ) 2025-10-10T00:01:08.3080742Z continue 2025-10-10T00:01:08.3080984Z 2025-10-10T00:01:08.3081257Z # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:01:08.3081860Z opted_out_users = [ 2025-10-10T00:01:08.3082293Z requestor 2025-10-10T00:01:08.3082735Z for requestor in workflow_requestors 2025-10-10T00:01:08.3083386Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.3083998Z ] 2025-10-10T00:01:08.3084204Z 2025-10-10T00:01:08.3084379Z if opted_out_users: 2025-10-10T00:01:08.3084819Z log.info( 2025-10-10T00:01:08.3085429Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:01:08.3086111Z ) 2025-10-10T00:01:08.3086477Z continue 2025-10-10T00:01:08.3086733Z 2025-10-10T00:01:08.3087014Z # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:01:08.3087632Z opted_in_users = [ 2025-10-10T00:01:08.3088077Z requestor 2025-10-10T00:01:08.3088665Z for requestor in workflow_requestors 2025-10-10T00:01:08.3089336Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.3089940Z ] 2025-10-10T00:01:08.3090153Z 2025-10-10T00:01:08.3090321Z enabled = False 2025-10-10T00:01:08.3090748Z if opted_in_users: 2025-10-10T00:01:08.3091178Z log.info( 2025-10-10T00:01:08.3092215Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:01:08.3093020Z ) 2025-10-10T00:01:08.3093413Z enabled = True 2025-10-10T00:01:08.3093690Z 2025-10-10T00:01:08.3093900Z elif experiment_settings.rollout_perc: 2025-10-10T00:01:08.3094728Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:01:08.3095810Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:01:08.3096650Z log.info( 2025-10-10T00:01:08.3097624Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:01:08.3098659Z ) 2025-10-10T00:01:08.3099064Z enabled = True 2025-10-10T00:01:08.3099355Z 2025-10-10T00:01:08.3099518Z if enabled: 2025-10-10T00:01:08.3099934Z label = experiment_name 2025-10-10T00:01:08.3100475Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:01:08.3101302Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:01:08.3102181Z # - If it's enabled, then we always list it's prefix first 2025-10-10T00:01:08.3102929Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:01:08.3103587Z if is_canary: 2025-10-10T00:01:08.3104065Z label += CANARY_FLEET_SUFFIX 2025-10-10T00:01:08.3104606Z fleet_prefix = label 2025-10-10T00:01:08.3105084Z else: 2025-10-10T00:01:08.3105504Z prefixes.append(label) 2025-10-10T00:01:08.3105849Z 2025-10-10T00:01:08.3106031Z if len(prefixes) > 1: 2025-10-10T00:01:08.3106458Z log.error( 2025-10-10T00:01:08.3107477Z 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-10-10T00:01:08.3108704Z ) 2025-10-10T00:01:08.3109094Z prefixes = prefixes[:1] 2025-10-10T00:01:08.3109396Z 2025-10-10T00:01:08.3109579Z # Fleet always comes first 2025-10-10T00:01:08.3110045Z if fleet_prefix: 2025-10-10T00:01:08.3110487Z prefixes.insert(0, fleet_prefix) 2025-10-10T00:01:08.3110849Z 2025-10-10T00:01:08.3111237Z return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:01:08.3111656Z 2025-10-10T00:01:08.3111663Z 2025-10-10T00:01:08.3112108Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:01:08.3112862Z """ 2025-10-10T00:01:08.3113438Z Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:01:08.3113987Z 2025-10-10T00:01:08.3114370Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:01:08.3115052Z """ 2025-10-10T00:01:08.3115436Z gh = get_gh_client(github_token) 2025-10-10T00:01:08.3115963Z issue = get_issue(gh, repo, issue_num) 2025-10-10T00:01:08.3116589Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:01:08.3117029Z 2025-10-10T00:01:08.3117036Z 2025-10-10T00:01:08.3117433Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:01:08.3118194Z for _ in range(num_retries): 2025-10-10T00:01:08.3118819Z try: 2025-10-10T00:01:08.3119244Z req = Request(url=url, headers=headers) 2025-10-10T00:01:08.3119906Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:01:08.3120533Z return json.loads(content) 2025-10-10T00:01:08.3121051Z except Exception as e: 2025-10-10T00:01:08.3121577Z log.warning(f"Could not download {url}: {e}") 2025-10-10T00:01:08.3121981Z 2025-10-10T00:01:08.3122352Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:01:08.3123057Z return {} 2025-10-10T00:01:08.3123282Z 2025-10-10T00:01:08.3123289Z 2025-10-10T00:01:08.3123438Z @cache 2025-10-10T00:01:08.3124048Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:01:08.3124786Z """ 2025-10-10T00:01:08.3125170Z Dynamically get PR information 2025-10-10T00:01:08.3125642Z """ 2025-10-10T00:01:08.3126282Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:01:08.3126889Z headers = { 2025-10-10T00:01:08.3127337Z "Accept": "application/vnd.github.v3+json", 2025-10-10T00:01:08.3127926Z "Authorization": f"token {github_token}", 2025-10-10T00:01:08.3128583Z } 2025-10-10T00:01:08.3129009Z json_response: dict[str, Any] = download_json( 2025-10-10T00:01:08.3129602Z url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:01:08.3130143Z headers=headers, 2025-10-10T00:01:08.3130552Z ) 2025-10-10T00:01:08.3130747Z 2025-10-10T00:01:08.3130927Z if not json_response: 2025-10-10T00:01:08.3131479Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:01:08.3132082Z return {} 2025-10-10T00:01:08.3132312Z 2025-10-10T00:01:08.3132483Z return json_response 2025-10-10T00:01:08.3132762Z 2025-10-10T00:01:08.3132768Z 2025-10-10T00:01:08.3133160Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:01:08.3133892Z """ 2025-10-10T00:01:08.3134403Z Dynamically get the latest list of labels from the pull request 2025-10-10T00:01:08.3135045Z """ 2025-10-10T00:01:08.3135509Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:01:08.3136108Z return { 2025-10-10T00:01:08.3136686Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:01:08.3137379Z } 2025-10-10T00:01:08.3137575Z 2025-10-10T00:01:08.3137581Z 2025-10-10T00:01:08.3137751Z def main() -> None: 2025-10-10T00:01:08.3138162Z args = parse_args() 2025-10-10T00:01:08.3138551Z 2025-10-10T00:01:08.3138776Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:01:08.3139186Z 2025-10-10T00:01:08.3139372Z # Check if the PR is opt-out 2025-10-10T00:01:08.3139856Z if args.pr_number: 2025-10-10T00:01:08.3140490Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:01:08.3141368Z if OPT_OUT_LABEL in labels: 2025-10-10T00:01:08.3141856Z log.info( 2025-10-10T00:01:08.3142537Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:01:08.3143296Z ) 2025-10-10T00:01:08.3143833Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.3144700Z sys.exit() 2025-10-10T00:01:08.3144960Z 2025-10-10T00:01:08.3145121Z try: 2025-10-10T00:01:08.3145557Z rollout_state = get_rollout_state_from_issue( 2025-10-10T00:01:08.3146244Z args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:01:08.3146867Z ) 2025-10-10T00:01:08.3147066Z 2025-10-10T00:01:08.3147273Z username = get_potential_pr_author( 2025-10-10T00:01:08.3147803Z args.github_token, 2025-10-10T00:01:08.3148275Z args.github_repo, 2025-10-10T00:01:08.3148857Z args.github_actor, 2025-10-10T00:01:08.3149343Z args.github_ref_type, 2025-10-10T00:01:08.3149996Z args.github_branch, 2025-10-10T00:01:08.3150452Z ) 2025-10-10T00:01:08.3150655Z 2025-10-10T00:01:08.3150935Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:01:08.3151385Z 2025-10-10T00:01:08.3151598Z runner_label_prefix = get_runner_prefix( 2025-10-10T00:01:08.3152140Z rollout_state, 2025-10-10T00:01:08.3152609Z (args.github_issue_owner, username), 2025-10-10T00:01:08.3153141Z args.github_branch, 2025-10-10T00:01:08.3153621Z args.eligible_experiments, 2025-10-10T00:01:08.3154146Z args.opt_out_experiments, 2025-10-10T00:01:08.3154634Z is_canary, 2025-10-10T00:01:08.3155039Z ) 2025-10-10T00:01:08.3155240Z 2025-10-10T00:01:08.3155418Z except Exception as e: 2025-10-10T00:01:08.3155860Z log.error( 2025-10-10T00:01:08.3156521Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:01:08.3157546Z ) 2025-10-10T00:01:08.3157753Z 2025-10-10T00:01:08.3158084Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.3158686Z 2025-10-10T00:01:08.3158693Z 2025-10-10T00:01:08.3158869Z if __name__ == "__main__": 2025-10-10T00:01:08.3159300Z main() 2025-10-10T00:01:08.3159508Z 2025-10-10T00:01:08.3251107Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:01:08.3252002Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:01:08.3284575Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:08.3285039Z env: 2025-10-10T00:01:08.3285661Z GITHUB_TOKEN: *** 2025-10-10T00:01:08.3286072Z ISSUE_NUMBER: 5132 2025-10-10T00:01:08.3286506Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:08.3286998Z ISSUE_OWNER: 2025-10-10T00:01:08.3287392Z CHECK_EXPERIMENTS: 2025-10-10T00:01:08.3287823Z OPT_OUT_EXPERIMENTS: lf 2025-10-10T00:01:08.3288255Z PR_NUMBER: 2025-10-10T00:01:08.3288782Z ##[endgroup] 2025-10-10T00:01:09.9781951Z Defaulting to user installation because normal site-packages is not writeable 2025-10-10T00:01:11.2900399Z Collecting urllib3==1.26.18 2025-10-10T00:01:11.3510309Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-10-10T00:01:11.3746253Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.8 MB/s eta 0:00:00 2025-10-10T00:01:11.4026467Z Collecting PyGithub==2.3.0 2025-10-10T00:01:11.4098898Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-10-10T00:01:11.4580122Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-10-10T00:01:11.4652182Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-10-10T00:01:11.4713383Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-10-10T00:01:11.4729583Z 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-10-10T00:01:11.4744122Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-10-10T00:01:11.5054524Z Collecting Deprecated (from PyGithub==2.3.0) 2025-10-10T00:01:11.5130372Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-10-10T00:01:11.5364567Z 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-10-10T00:01:11.6680581Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:01:11.6764155Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-10-10T00:01:11.8146897Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-10-10T00:01:11.8247762Z 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-10-10T00:01:11.8488597Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:01:11.8586105Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-10-10T00:01:11.8872692Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-10-10T00:01:11.8979869Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 15.2 MB/s eta 0:00:00 2025-10-10T00:01:11.9051506Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-10-10T00:01:11.9184043Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 32.0 MB/s eta 0:00:00 2025-10-10T00:01:11.9261897Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-10-10T00:01:11.9424615Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 94.6 MB/s eta 0:00:00 2025-10-10T00:01:11.9573049Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-10-10T00:01:11.9722410Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-10-10T00:01:11.9775225Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 57.2 MB/s eta 0:00:00 2025-10-10T00:01:11.9889002Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-10-10T00:01:11.9935937Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 26.2 MB/s eta 0:00:00 2025-10-10T00:01:12.0131606Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-10-10T00:01:12.0178576Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 34.8 MB/s eta 0:00:00 2025-10-10T00:01:12.3268144Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-10-10T00:01:12.8685591Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.0 urllib3-1.26.18 wrapt-1.17.3 2025-10-10T00:01:12.9514609Z ##[group]Run curr_branch="main" 2025-10-10T00:01:12.9514934Z curr_branch="main" 2025-10-10T00:01:12.9515161Z curr_ref_type="branch" 2025-10-10T00:01:12.9515417Z echo "Current branch is '$curr_branch'" 2025-10-10T00:01:12.9515697Z  2025-10-10T00:01:12.9515894Z python3 runner_determinator.py \ 2025-10-10T00:01:12.9516191Z  --github-token "$GITHUB_TOKEN" \ 2025-10-10T00:01:12.9516462Z  --github-issue "$ISSUE_NUMBER" \ 2025-10-10T00:01:12.9516730Z  --github-branch "$curr_branch" \ 2025-10-10T00:01:12.9517002Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-10-10T00:01:12.9517276Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-10-10T00:01:12.9517560Z  --github-ref-type "$curr_ref_type" \ 2025-10-10T00:01:12.9517834Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-10-10T00:01:12.9518133Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-10-10T00:01:12.9518705Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-10-10T00:01:12.9519009Z  --pr-number "${PR_NUMBER}" 2025-10-10T00:01:12.9553829Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:12.9554190Z env: 2025-10-10T00:01:12.9555002Z GITHUB_TOKEN: *** 2025-10-10T00:01:12.9555328Z ISSUE_NUMBER: 5132 2025-10-10T00:01:12.9555673Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:12.9556058Z ISSUE_OWNER: 2025-10-10T00:01:12.9556353Z CHECK_EXPERIMENTS: 2025-10-10T00:01:12.9556687Z OPT_OUT_EXPERIMENTS: lf 2025-10-10T00:01:12.9557025Z PR_NUMBER: 2025-10-10T00:01:12.9557306Z ##[endgroup] 2025-10-10T00:01:12.9610296Z Current branch is 'main' 2025-10-10T00:01:14.3298090Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-10-10T00:01:14.3299746Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-10-10T00:01:14.3300687Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-10-10T00:01:14.3301572Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-10-10T00:01:14.3302264Z INFO : Setting output: label-type='' 2025-10-10T00:01:14.3625279Z Evaluate and set job outputs 2025-10-10T00:01:14.3632651Z Cleaning up orphan processes