2025-03-14T03:57:17.5680896Z Current runner version: '2.322.0' 2025-03-14T03:57:17.5705305Z ##[group]Operating System 2025-03-14T03:57:17.5706287Z Ubuntu 2025-03-14T03:57:17.5706863Z 24.04.2 2025-03-14T03:57:17.5707356Z LTS 2025-03-14T03:57:17.5707809Z ##[endgroup] 2025-03-14T03:57:17.5708349Z ##[group]Runner Image 2025-03-14T03:57:17.5708928Z Image: ubuntu-24.04 2025-03-14T03:57:17.5709434Z Version: 20250309.1.0 2025-03-14T03:57:17.5710531Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250309.1/images/ubuntu/Ubuntu2404-Readme.md 2025-03-14T03:57:17.5712151Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250309.1 2025-03-14T03:57:17.5713107Z ##[endgroup] 2025-03-14T03:57:17.5713634Z ##[group]Runner Image Provisioner 2025-03-14T03:57:17.5714225Z 2.0.422.1 2025-03-14T03:57:17.5714727Z ##[endgroup] 2025-03-14T03:57:17.5715722Z ##[group]GITHUB_TOKEN Permissions 2025-03-14T03:57:17.5717617Z Contents: read 2025-03-14T03:57:17.5718279Z Metadata: read 2025-03-14T03:57:17.5718973Z ##[endgroup] 2025-03-14T03:57:17.5722145Z Secret source: Actions 2025-03-14T03:57:17.5722881Z Prepare workflow directory 2025-03-14T03:57:17.6196268Z Prepare all required actions 2025-03-14T03:57:17.6249624Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (aed0b7a742a2d7b7901790622829cbd2135049a4) 2025-03-14T03:57:17.6254904Z ##[group] Inputs 2025-03-14T03:57:17.6255498Z check_experiments: 2025-03-14T03:57:17.6256244Z triggering_actor: pytorchmergebot 2025-03-14T03:57:17.6256899Z issue_owner: 2025-03-14T03:57:17.6257397Z curr_branch: main 2025-03-14T03:57:17.6258021Z curr_ref_type: branch 2025-03-14T03:57:17.6258570Z issue_number: 5132 2025-03-14T03:57:17.6259127Z ##[endgroup] 2025-03-14T03:57:17.6259800Z Complete job name: before-test / get-label-type / runner-determinator 2025-03-14T03:57:18.1917840Z ##[group]Run cat < runner_determinator.py 2025-03-14T03:57:18.1919541Z cat < runner_determinator.py 2025-03-14T03:57:18.1920167Z # flake8: noqa: G004 2025-03-14T03:57:18.1920696Z  2025-03-14T03:57:18.1921609Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-03-14T03:57:18.1922704Z # must be kept in sync. You can do it easily by running the following command: 2025-03-14T03:57:18.1923604Z # python .github/scripts/update_runner_determinator.py 2025-03-14T03:57:18.1924287Z  2025-03-14T03:57:18.1924679Z """ 2025-03-14T03:57:18.1925358Z This runner determinator is used to determine which set of runners to run a 2025-03-14T03:57:18.1926370Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-03-14T03:57:18.1927441Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-03-14T03:57:18.1928403Z of which runners should be used to run which job. 2025-03-14T03:57:18.1929045Z  2025-03-14T03:57:18.1929700Z The configuration has two parts, the settings and a list of opted-in users, 2025-03-14T03:57:18.1930729Z separated by a line containing "---". If the line is not present, the 2025-03-14T03:57:18.1931831Z settings are considered to be empty with only the second part, the user 2025-03-14T03:57:18.1932616Z list, defined. 2025-03-14T03:57:18.1933354Z  2025-03-14T03:57:18.1934028Z The first part is a YAML block that defines the rollout settings. This can be 2025-03-14T03:57:18.1935072Z used to define any settings that are needed to determine which runners to use. 2025-03-14T03:57:18.1936022Z It's fields are defined by the RolloutSettings class below. 2025-03-14T03:57:18.1936701Z  2025-03-14T03:57:18.1937358Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-03-14T03:57:18.1938349Z The user list is also a comma separated list of additional features or 2025-03-14T03:57:18.1939440Z experiments which the user could be opted in to. 2025-03-14T03:57:18.1940077Z  2025-03-14T03:57:18.1940543Z The user list has the following rules: 2025-03-14T03:57:18.1941132Z  2025-03-14T03:57:18.1941922Z - Users are GitHub usernames, which must start with the @ prefix 2025-03-14T03:57:18.1942915Z - Each user is also a comma-separated list of features/experiments to enable 2025-03-14T03:57:18.1943799Z - A "#" prefix opts the user out of all experiments 2025-03-14T03:57:18.1944431Z  2025-03-14T03:57:18.1944961Z Example config: 2025-03-14T03:57:18.1945541Z  # A list of experiments that can be opted into. 2025-03-14T03:57:18.1946320Z  # This defines the behavior they'll induce when opted into. 2025-03-14T03:57:18.1947036Z  # Expected syntax is: 2025-03-14T03:57:18.1947807Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-03-14T03:57:18.1948898Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-03-14T03:57:18.1949736Z  2025-03-14T03:57:18.1950152Z  experiments: 2025-03-14T03:57:18.1950633Z  lf: 2025-03-14T03:57:18.1951091Z  rollout_percent: 25 2025-03-14T03:57:18.1951758Z  all_branches: false 2025-03-14T03:57:18.1952300Z  default: true 2025-03-14T03:57:18.1952801Z  --- 2025-03-14T03:57:18.1953224Z  2025-03-14T03:57:18.1953630Z  # Opt-ins: 2025-03-14T03:57:18.1954324Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-03-14T03:57:18.1955489Z  # and specifying experiments to enable in a comma-separated list. 2025-03-14T03:57:18.1956400Z  # To always opt out of an experiment, prefix it with a "-". 2025-03-14T03:57:18.1957161Z  # Experiments should be from the above list. 2025-03-14T03:57:18.1957773Z  2025-03-14T03:57:18.1958192Z  @User1,-lf,split_build 2025-03-14T03:57:18.1958718Z  @User2,lf 2025-03-14T03:57:18.1959209Z  @User3,split_build 2025-03-14T03:57:18.1959732Z """ 2025-03-14T03:57:18.1960137Z  2025-03-14T03:57:18.1960545Z import json 2025-03-14T03:57:18.1960996Z import logging 2025-03-14T03:57:18.1961584Z import os 2025-03-14T03:57:18.1962046Z import random 2025-03-14T03:57:18.1962520Z import re 2025-03-14T03:57:18.1962968Z import sys 2025-03-14T03:57:18.1963480Z from argparse import ArgumentParser 2025-03-14T03:57:18.1964127Z from collections.abc import Iterable 2025-03-14T03:57:18.1964744Z from functools import cache 2025-03-14T03:57:18.1965321Z from logging import LogRecord 2025-03-14T03:57:18.1965918Z from typing import Any, NamedTuple 2025-03-14T03:57:18.1966589Z from urllib.request import Request, urlopen 2025-03-14T03:57:18.1967200Z  2025-03-14T03:57:18.1967605Z import yaml 2025-03-14T03:57:18.1968105Z from github import Auth, Github 2025-03-14T03:57:18.1968698Z from github.Issue import Issue 2025-03-14T03:57:18.1969238Z  2025-03-14T03:57:18.1969633Z  2025-03-14T03:57:18.1970123Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-03-14T03:57:18.1970930Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-03-14T03:57:18.1972018Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-03-14T03:57:18.1972815Z  2025-03-14T03:57:18.1973312Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-03-14T03:57:18.1973979Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-03-14T03:57:18.1974603Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-03-14T03:57:18.1975422Z OPT_OUT_LABEL = "no-runner-experiments" 2025-03-14T03:57:18.1976229Z  2025-03-14T03:57:18.1976679Z SETTING_EXPERIMENTS = "experiments" 2025-03-14T03:57:18.1977248Z  2025-03-14T03:57:18.1977670Z LF_FLEET_EXPERIMENT = "lf" 2025-03-14T03:57:18.1978233Z CANARY_FLEET_SUFFIX = ".c" 2025-03-14T03:57:18.1978756Z  2025-03-14T03:57:18.1979136Z  2025-03-14T03:57:18.1979564Z class Experiment(NamedTuple): 2025-03-14T03:57:18.1980142Z  rollout_perc: float = ( 2025-03-14T03:57:18.1980917Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-03-14T03:57:18.1981841Z  ) 2025-03-14T03:57:18.1982339Z  all_branches: bool = ( 2025-03-14T03:57:18.1983109Z  False # If True, the experiment is also enabled on the exception branches 2025-03-14T03:57:18.1983915Z  ) 2025-03-14T03:57:18.1984355Z  default: bool = ( 2025-03-14T03:57:18.1985061Z  True # If True, the experiment is enabled by default for all queries 2025-03-14T03:57:18.1985790Z  ) 2025-03-14T03:57:18.1986205Z  2025-03-14T03:57:18.1986626Z  # Add more fields as needed 2025-03-14T03:57:18.1987170Z  2025-03-14T03:57:18.1987558Z  2025-03-14T03:57:18.1987983Z class Settings(NamedTuple): 2025-03-14T03:57:18.1988523Z  """ 2025-03-14T03:57:18.1989072Z  Settings for the experiments that can be opted into. 2025-03-14T03:57:18.1989731Z  """ 2025-03-14T03:57:18.1990146Z  2025-03-14T03:57:18.1990608Z  experiments: dict[str, Experiment] = {} 2025-03-14T03:57:18.1991189Z  2025-03-14T03:57:18.1991845Z  2025-03-14T03:57:18.1992335Z class ColorFormatter(logging.Formatter): 2025-03-14T03:57:18.1993069Z  """Color codes the log messages based on the log level""" 2025-03-14T03:57:18.1993744Z  2025-03-14T03:57:18.1994141Z  COLORS = { 2025-03-14T03:57:18.1994641Z  "WARNING": "\033[33m", # Yellow 2025-03-14T03:57:18.1995231Z  "ERROR": "\033[31m", # Red 2025-03-14T03:57:18.1995820Z  "CRITICAL": "\033[31m", # Red 2025-03-14T03:57:18.1996409Z  "INFO": "\033[0m", # Reset 2025-03-14T03:57:18.1996998Z  "DEBUG": "\033[0m", # Reset 2025-03-14T03:57:18.1997550Z  } 2025-03-14T03:57:18.1997959Z  2025-03-14T03:57:18.1998498Z  def format(self, record: LogRecord) -> str: 2025-03-14T03:57:18.1999428Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-03-14T03:57:18.2000369Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-03-14T03:57:18.2001092Z  return super().format(record) 2025-03-14T03:57:18.2001824Z  2025-03-14T03:57:18.2002273Z  2025-03-14T03:57:18.2002782Z handler = logging.StreamHandler() 2025-03-14T03:57:18.2003691Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-03-14T03:57:18.2004558Z  2025-03-14T03:57:18.2005147Z log = logging.getLogger(os.path.basename(__file__)) 2025-03-14T03:57:18.2005880Z log.addHandler(handler) 2025-03-14T03:57:18.2006489Z log.setLevel(logging.INFO) 2025-03-14T03:57:18.2007071Z  2025-03-14T03:57:18.2007520Z  2025-03-14T03:57:18.2008106Z def set_github_output(key: str, value: str) -> None: 2025-03-14T03:57:18.2008817Z  """ 2025-03-14T03:57:18.2009484Z  Defines outputs of the github action that invokes this script 2025-03-14T03:57:18.2010273Z  """ 2025-03-14T03:57:18.2010778Z  if not GITHUB_OUTPUT: 2025-03-14T03:57:18.2012158Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-03-14T03:57:18.2013543Z  log.warning( 2025-03-14T03:57:18.2014533Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-03-14T03:57:18.2015571Z  ) 2025-03-14T03:57:18.2016097Z  print(f"::set-output name={key}::{value}") 2025-03-14T03:57:18.2016725Z  return 2025-03-14T03:57:18.2017189Z  2025-03-14T03:57:18.2017640Z  with open(GITHUB_OUTPUT, "a") as f: 2025-03-14T03:57:18.2018314Z  log.info(f"Setting output: {key}='{value}'") 2025-03-14T03:57:18.2018977Z  f.write(f"{key}={value}\n") 2025-03-14T03:57:18.2019541Z  2025-03-14T03:57:18.2019944Z  2025-03-14T03:57:18.2020528Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-03-14T03:57:18.2021277Z  return frozenset( 2025-03-14T03:57:18.2022144Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-03-14T03:57:18.2022943Z  ) 2025-03-14T03:57:18.2023366Z  2025-03-14T03:57:18.2023840Z  2025-03-14T03:57:18.2024394Z def parse_args() -> Any: 2025-03-14T03:57:18.2025252Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-03-14T03:57:18.2026255Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-03-14T03:57:18.2027130Z  parser.add_argument( 2025-03-14T03:57:18.2027690Z  "--github-issue-repo", 2025-03-14T03:57:18.2028261Z  type=str, 2025-03-14T03:57:18.2028773Z  required=False, 2025-03-14T03:57:18.2029491Z  default="pytorch/test-infra", 2025-03-14T03:57:18.2030158Z  help="GitHub repo to get the issue", 2025-03-14T03:57:18.2030740Z  ) 2025-03-14T03:57:18.2031181Z  parser.add_argument( 2025-03-14T03:57:18.2031851Z  "--github-repo", 2025-03-14T03:57:18.2032446Z  type=str, 2025-03-14T03:57:18.2032968Z  required=True, 2025-03-14T03:57:18.2033539Z  help="GitHub repo where CI is running", 2025-03-14T03:57:18.2034139Z  ) 2025-03-14T03:57:18.2034574Z  parser.add_argument( 2025-03-14T03:57:18.2035319Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-03-14T03:57:18.2036063Z  ) 2025-03-14T03:57:18.2036508Z  parser.add_argument( 2025-03-14T03:57:18.2037259Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-03-14T03:57:18.2038036Z  ) 2025-03-14T03:57:18.2038475Z  parser.add_argument( 2025-03-14T03:57:18.2039234Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-03-14T03:57:18.2040023Z  ) 2025-03-14T03:57:18.2040467Z  parser.add_argument( 2025-03-14T03:57:18.2041257Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-03-14T03:57:18.2042174Z  ) 2025-03-14T03:57:18.2042610Z  parser.add_argument( 2025-03-14T03:57:18.2043148Z  "--github-ref-type", 2025-03-14T03:57:18.2043697Z  type=str, 2025-03-14T03:57:18.2044190Z  required=True, 2025-03-14T03:57:18.2044798Z  help="Current GitHub ref type, branch or tag", 2025-03-14T03:57:18.2045423Z  ) 2025-03-14T03:57:18.2045866Z  parser.add_argument( 2025-03-14T03:57:18.2046446Z  "--eligible-experiments", 2025-03-14T03:57:18.2047062Z  type=_str_comma_separated_to_set, 2025-03-14T03:57:18.2047662Z  required=False, 2025-03-14T03:57:18.2048183Z  default="", 2025-03-14T03:57:18.2049323Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-03-14T03:57:18.2050346Z  ) 2025-03-14T03:57:18.2050788Z  parser.add_argument( 2025-03-14T03:57:18.2051329Z  "--pr-number", 2025-03-14T03:57:18.2051949Z  type=str, 2025-03-14T03:57:18.2052439Z  required=False, 2025-03-14T03:57:18.2052951Z  default="", 2025-03-14T03:57:18.2053550Z  help="the optional PR number where this is run", 2025-03-14T03:57:18.2054183Z  ) 2025-03-14T03:57:18.2054599Z  2025-03-14T03:57:18.2055034Z  return parser.parse_args() 2025-03-14T03:57:18.2055573Z  2025-03-14T03:57:18.2055967Z  2025-03-14T03:57:18.2056720Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-03-14T03:57:18.2057608Z  auth = Auth.Token(github_token) 2025-03-14T03:57:18.2058226Z  return Github(auth=auth) 2025-03-14T03:57:18.2058758Z  2025-03-14T03:57:18.2059149Z  2025-03-14T03:57:18.2059899Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-03-14T03:57:18.2060817Z  repo = gh.get_repo(repo) 2025-03-14T03:57:18.2061531Z  return repo.get_issue(number=issue_num) 2025-03-14T03:57:18.2062133Z  2025-03-14T03:57:18.2062514Z  2025-03-14T03:57:18.2062938Z def get_potential_pr_author( 2025-03-14T03:57:18.2063706Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-03-14T03:57:18.2064513Z ) -> str: 2025-03-14T03:57:18.2065281Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-03-14T03:57:18.2066221Z  # Fetch the actual username from the original PR. The PR number is 2025-03-14T03:57:18.2067107Z  # embedded in the tag name: ciflow// 2025-03-14T03:57:18.2067760Z  2025-03-14T03:57:18.2068197Z  gh = get_gh_client(github_token) 2025-03-14T03:57:18.2068765Z  2025-03-14T03:57:18.2069301Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-03-14T03:57:18.2070025Z  split_tag = ref_name.split("/") 2025-03-14T03:57:18.2070609Z  if ( 2025-03-14T03:57:18.2071076Z  len(split_tag) == 3 2025-03-14T03:57:18.2071777Z  and split_tag[0] == "ciflow" 2025-03-14T03:57:18.2072397Z  and split_tag[2].isnumeric() 2025-03-14T03:57:18.2072969Z  ): 2025-03-14T03:57:18.2073454Z  pr_number = split_tag[2] 2025-03-14T03:57:18.2074022Z  try: 2025-03-14T03:57:18.2074542Z  repository = gh.get_repo(repo) 2025-03-14T03:57:18.2075270Z  pull = repository.get_pull(number=int(pr_number)) 2025-03-14T03:57:18.2075977Z  except Exception as e: 2025-03-14T03:57:18.2076601Z  raise Exception( # noqa: TRY002 2025-03-14T03:57:18.2077379Z  f"issue with pull request {pr_number} from repo {repository}" 2025-03-14T03:57:18.2078112Z  ) from e 2025-03-14T03:57:18.2078761Z  return pull.user.login # type: ignore[no-any-return] 2025-03-14T03:57:18.2079572Z  # In all other cases, return the original input username 2025-03-14T03:57:18.2080251Z  return username 2025-03-14T03:57:18.2080736Z  2025-03-14T03:57:18.2081111Z  2025-03-14T03:57:18.2081692Z def is_exception_branch(branch: str) -> bool: 2025-03-14T03:57:18.2082305Z  """ 2025-03-14T03:57:18.2083056Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-03-14T03:57:18.2084060Z  """ 2025-03-14T03:57:18.2084696Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-03-14T03:57:18.2085456Z  2025-03-14T03:57:18.2085832Z  2025-03-14T03:57:18.2086288Z def load_yaml(yaml_text: str) -> Any: 2025-03-14T03:57:18.2086854Z  try: 2025-03-14T03:57:18.2087311Z  data = yaml.safe_load(yaml_text) 2025-03-14T03:57:18.2087889Z  return data 2025-03-14T03:57:18.2088389Z  except yaml.YAMLError: 2025-03-14T03:57:18.2088982Z  log.exception("Error loading YAML") 2025-03-14T03:57:18.2089568Z  raise 2025-03-14T03:57:18.2090007Z  2025-03-14T03:57:18.2090388Z  2025-03-14T03:57:18.2091085Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-03-14T03:57:18.2092028Z  """ 2025-03-14T03:57:18.2092779Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-03-14T03:57:18.2093641Z  2025-03-14T03:57:18.2094257Z  If the issue body contains "---" then the text above that is the settings 2025-03-14T03:57:18.2095136Z  and the text below is the list of opted in users. 2025-03-14T03:57:18.2095765Z  2025-03-14T03:57:18.2096410Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-03-14T03:57:18.2097202Z  """ 2025-03-14T03:57:18.2097733Z  rollout_state_parts = rollout_state.split("---") 2025-03-14T03:57:18.2098419Z  if len(rollout_state_parts) >= 2: 2025-03-14T03:57:18.2099263Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-03-14T03:57:18.2099954Z  else: 2025-03-14T03:57:18.2100416Z  return "", rollout_state 2025-03-14T03:57:18.2100960Z  2025-03-14T03:57:18.2101346Z  2025-03-14T03:57:18.2102032Z class UserOptins(dict[str, list[str]]): 2025-03-14T03:57:18.2102624Z  """ 2025-03-14T03:57:18.2103255Z  Dictionary of users with a list of features they have opted into 2025-03-14T03:57:18.2103993Z  """ 2025-03-14T03:57:18.2104404Z  2025-03-14T03:57:18.2104778Z  2025-03-14T03:57:18.2105391Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-03-14T03:57:18.2106143Z  """ 2025-03-14T03:57:18.2106980Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-03-14T03:57:18.2107913Z  2025-03-14T03:57:18.2108820Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-03-14T03:57:18.2109939Z  - Example line: "@User1,lf,split_build" 2025-03-14T03:57:18.2110720Z  - A "#" prefix indicates the user is opted out of all experiments 2025-03-14T03:57:18.2111543Z  2025-03-14T03:57:18.2111928Z  2025-03-14T03:57:18.2112315Z  """ 2025-03-14T03:57:18.2112758Z  optins = UserOptins() 2025-03-14T03:57:18.2113360Z  for user in user_optin_text.split("\n"): 2025-03-14T03:57:18.2114011Z  user = user.strip("\r\n\t -") 2025-03-14T03:57:18.2114657Z  if not user or not user.startswith("@"): 2025-03-14T03:57:18.2115299Z  # Not a valid user. Skip 2025-03-14T03:57:18.2115868Z  continue 2025-03-14T03:57:18.2116339Z  2025-03-14T03:57:18.2116739Z  if user: 2025-03-14T03:57:18.2117288Z  usr_name = user.split(",")[0].strip("@") 2025-03-14T03:57:18.2118087Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-03-14T03:57:18.2118804Z  2025-03-14T03:57:18.2119354Z  return optins 2025-03-14T03:57:18.2119823Z  2025-03-14T03:57:18.2120208Z  2025-03-14T03:57:18.2120778Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-03-14T03:57:18.2121577Z  """ 2025-03-14T03:57:18.2122066Z  Check if the experiment name is valid. 2025-03-14T03:57:18.2122690Z  A valid name: 2025-03-14T03:57:18.2123463Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-03-14T03:57:18.2124529Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-03-14T03:57:18.2125342Z  - Cannot contain spaces 2025-03-14T03:57:18.2125891Z  """ 2025-03-14T03:57:18.2126298Z  2025-03-14T03:57:18.2126820Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-03-14T03:57:18.2127636Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-03-14T03:57:18.2128328Z  2025-03-14T03:57:18.2128720Z  if valid: 2025-03-14T03:57:18.2129195Z  return True 2025-03-14T03:57:18.2129665Z  2025-03-14T03:57:18.2130055Z  log.error( 2025-03-14T03:57:18.2131800Z  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-03-14T03:57:18.2133511Z  ) 2025-03-14T03:57:18.2133935Z  return False 2025-03-14T03:57:18.2134393Z  2025-03-14T03:57:18.2134825Z  2025-03-14T03:57:18.2135584Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-03-14T03:57:18.2136350Z  """ 2025-03-14T03:57:18.2137043Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-03-14T03:57:18.2137853Z  """ 2025-03-14T03:57:18.2138258Z  try: 2025-03-14T03:57:18.2138695Z  if settings_text: 2025-03-14T03:57:18.2139542Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-03-14T03:57:18.2140427Z  # for easy reading 2025-03-14T03:57:18.2141351Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-03-14T03:57:18.2142470Z  # the backtick character in shell commands. 2025-03-14T03:57:18.2143164Z  backtick = chr(96) # backtick character 2025-03-14T03:57:18.2143936Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-03-14T03:57:18.2144698Z  settings = load_yaml(settings_text) 2025-03-14T03:57:18.2145289Z  2025-03-14T03:57:18.2145962Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-03-14T03:57:18.2146802Z  experiments = {} 2025-03-14T03:57:18.2147321Z  2025-03-14T03:57:18.2147955Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-03-14T03:57:18.2148818Z  if not is_valid_experiment_name(exp_name): 2025-03-14T03:57:18.2150046Z  # 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-03-14T03:57:18.2151210Z  continue 2025-03-14T03:57:18.2151839Z  2025-03-14T03:57:18.2152263Z  valid_settings = {} 2025-03-14T03:57:18.2152884Z  for setting in exp_settings: 2025-03-14T03:57:18.2153543Z  if setting not in Experiment._fields: 2025-03-14T03:57:18.2154184Z  log.warning( 2025-03-14T03:57:18.2155138Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-03-14T03:57:18.2155937Z  ) 2025-03-14T03:57:18.2156445Z  else: 2025-03-14T03:57:18.2157070Z  valid_settings[setting] = exp_settings[setting] 2025-03-14T03:57:18.2157717Z  2025-03-14T03:57:18.2158255Z  experiments[exp_name] = Experiment(**valid_settings) 2025-03-14T03:57:18.2158988Z  return Settings(experiments) 2025-03-14T03:57:18.2159547Z  2025-03-14T03:57:18.2159953Z  except Exception: 2025-03-14T03:57:18.2160536Z  log.exception("Failed to parse settings") 2025-03-14T03:57:18.2161153Z  2025-03-14T03:57:18.2161665Z  return Settings() 2025-03-14T03:57:18.2162154Z  2025-03-14T03:57:18.2162537Z  2025-03-14T03:57:18.2163057Z def parse_settings(rollout_state: str) -> Settings: 2025-03-14T03:57:18.2163707Z  """ 2025-03-14T03:57:18.2164229Z  Parse settings, if any, from the rollout state. 2025-03-14T03:57:18.2164859Z  2025-03-14T03:57:18.2165460Z  If the issue body contains "---" then the text above that is the settings 2025-03-14T03:57:18.2166330Z  and the text below is the list of opted in users. 2025-03-14T03:57:18.2166958Z  2025-03-14T03:57:18.2167634Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-03-14T03:57:18.2168451Z  """ 2025-03-14T03:57:18.2169091Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:57:18.2170185Z  return parse_settings_from_text(settings_text) 2025-03-14T03:57:18.2170825Z  2025-03-14T03:57:18.2171203Z  2025-03-14T03:57:18.2171820Z def parse_users(rollout_state: str) -> UserOptins: 2025-03-14T03:57:18.2172477Z  """ 2025-03-14T03:57:18.2172941Z  Parse users from the rollout state. 2025-03-14T03:57:18.2173520Z  2025-03-14T03:57:18.2173899Z  """ 2025-03-14T03:57:18.2174529Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:57:18.2175379Z  return parse_user_opt_in_from_text(users_text) 2025-03-14T03:57:18.2175996Z  2025-03-14T03:57:18.2176381Z  2025-03-14T03:57:18.2177081Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:57:18.2177923Z  """ 2025-03-14T03:57:18.2178424Z  Check if a user is opted into an experiment 2025-03-14T03:57:18.2179040Z  """ 2025-03-14T03:57:18.2179588Z  return experiment_name in user_optins.get(user, []) 2025-03-14T03:57:18.2180244Z  2025-03-14T03:57:18.2180625Z  2025-03-14T03:57:18.2181333Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:57:18.2182282Z  """ 2025-03-14T03:57:18.2182827Z  Check if a user explicitly opted out of an experiment 2025-03-14T03:57:18.2183484Z  """ 2025-03-14T03:57:18.2184088Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-03-14T03:57:18.2184890Z  experiment_optout = "-" + experiment_name 2025-03-14T03:57:18.2185638Z  if experiment_optout not in user_optins.get(user, []): 2025-03-14T03:57:18.2186310Z  return False 2025-03-14T03:57:18.2186789Z  2025-03-14T03:57:18.2187395Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-03-14T03:57:18.2188293Z  log.warning( 2025-03-14T03:57:18.2189228Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-03-14T03:57:18.2190364Z  ) 2025-03-14T03:57:18.2190781Z  2025-03-14T03:57:18.2191178Z  return True 2025-03-14T03:57:18.2191759Z  2025-03-14T03:57:18.2192138Z  2025-03-14T03:57:18.2192545Z def get_runner_prefix( 2025-03-14T03:57:18.2193066Z  rollout_state: str, 2025-03-14T03:57:18.2193637Z  workflow_requestors: Iterable[str], 2025-03-14T03:57:18.2194231Z  branch: str, 2025-03-14T03:57:18.2194832Z  eligible_experiments: frozenset[str] = frozenset(), 2025-03-14T03:57:18.2195520Z  is_canary: bool = False, 2025-03-14T03:57:18.2196044Z ) -> str: 2025-03-14T03:57:18.2196557Z  settings = parse_settings(rollout_state) 2025-03-14T03:57:18.2197225Z  user_optins = parse_users(rollout_state) 2025-03-14T03:57:18.2197815Z  2025-03-14T03:57:18.2198254Z  fleet_prefix = "" 2025-03-14T03:57:18.2199147Z  prefixes = [] 2025-03-14T03:57:18.2200411Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-03-14T03:57:18.2201648Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-03-14T03:57:18.2202496Z  log.info( 2025-03-14T03:57:18.2203297Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-03-14T03:57:18.2204154Z  ) 2025-03-14T03:57:18.2204607Z  continue 2025-03-14T03:57:18.2205089Z  2025-03-14T03:57:18.2205511Z  if eligible_experiments: 2025-03-14T03:57:18.2206340Z  if experiment_name not in eligible_experiments: 2025-03-14T03:57:18.2207100Z  exp_list = ", ".join(eligible_experiments) 2025-03-14T03:57:18.2207733Z  log.info( 2025-03-14T03:57:18.2208650Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-03-14T03:57:18.2209594Z  ) 2025-03-14T03:57:18.2210082Z  continue 2025-03-14T03:57:18.2210669Z  elif not experiment_settings.default: 2025-03-14T03:57:18.2211341Z  log.info( 2025-03-14T03:57:18.2212538Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-03-14T03:57:18.2213366Z  ) 2025-03-14T03:57:18.2213868Z  continue 2025-03-14T03:57:18.2214349Z  2025-03-14T03:57:18.2214886Z  # Is any workflow_requestor opted out to this experiment? 2025-03-14T03:57:18.2215590Z  opted_out_users = [ 2025-03-14T03:57:18.2216126Z  requestor 2025-03-14T03:57:18.2216684Z  for requestor in workflow_requestors 2025-03-14T03:57:18.2217459Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-03-14T03:57:18.2218163Z  ] 2025-03-14T03:57:18.2218590Z  2025-03-14T03:57:18.2218993Z  if opted_out_users: 2025-03-14T03:57:18.2219528Z  log.info( 2025-03-14T03:57:18.2220260Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-03-14T03:57:18.2221035Z  ) 2025-03-14T03:57:18.2221609Z  continue 2025-03-14T03:57:18.2222086Z  2025-03-14T03:57:18.2222636Z  # Is any workflow_requestor opted in to this experiment? 2025-03-14T03:57:18.2223324Z  opted_in_users = [ 2025-03-14T03:57:18.2223860Z  requestor 2025-03-14T03:57:18.2224416Z  for requestor in workflow_requestors 2025-03-14T03:57:18.2225176Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-03-14T03:57:18.2226058Z  ] 2025-03-14T03:57:18.2226485Z  2025-03-14T03:57:18.2226893Z  enabled = False 2025-03-14T03:57:18.2227424Z  if opted_in_users: 2025-03-14T03:57:18.2227955Z  log.info( 2025-03-14T03:57:18.2228664Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-03-14T03:57:18.2229434Z  ) 2025-03-14T03:57:18.2229911Z  enabled = True 2025-03-14T03:57:18.2230425Z  2025-03-14T03:57:18.2230887Z  elif experiment_settings.rollout_perc: 2025-03-14T03:57:18.2231979Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-03-14T03:57:18.2233019Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-03-14T03:57:18.2233757Z  log.info( 2025-03-14T03:57:18.2234768Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-03-14T03:57:18.2235792Z  ) 2025-03-14T03:57:18.2236286Z  enabled = True 2025-03-14T03:57:18.2236813Z  2025-03-14T03:57:18.2237211Z  if enabled: 2025-03-14T03:57:18.2237734Z  label = experiment_name 2025-03-14T03:57:18.2238377Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-03-14T03:57:18.2239309Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-03-14T03:57:18.2240413Z  # - If it's enabled, then we always list it's prefix first 2025-03-14T03:57:18.2241290Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-03-14T03:57:18.2242399Z  if is_canary: 2025-03-14T03:57:18.2242996Z  label += CANARY_FLEET_SUFFIX 2025-03-14T03:57:18.2243613Z  fleet_prefix = label 2025-03-14T03:57:18.2244175Z  else: 2025-03-14T03:57:18.2244698Z  prefixes.append(label) 2025-03-14T03:57:18.2245269Z  2025-03-14T03:57:18.2245712Z  if len(prefixes) > 1: 2025-03-14T03:57:18.2246246Z  log.error( 2025-03-14T03:57:18.2247427Z  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-03-14T03:57:18.2248643Z  ) 2025-03-14T03:57:18.2249109Z  prefixes = prefixes[:1] 2025-03-14T03:57:18.2249655Z  2025-03-14T03:57:18.2250068Z  # Fleet always comes first 2025-03-14T03:57:18.2250624Z  if fleet_prefix: 2025-03-14T03:57:18.2251166Z  prefixes.insert(0, fleet_prefix) 2025-03-14T03:57:18.2252011Z  2025-03-14T03:57:18.2252534Z  return ".".join(prefixes) + "." if prefixes else "" 2025-03-14T03:57:18.2253178Z  2025-03-14T03:57:18.2253561Z  2025-03-14T03:57:18.2254271Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-03-14T03:57:18.2255132Z  """ 2025-03-14T03:57:18.2255810Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-03-14T03:57:18.2256587Z  2025-03-14T03:57:18.2257224Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-03-14T03:57:18.2258004Z  """ 2025-03-14T03:57:18.2258477Z  gh = get_gh_client(github_token) 2025-03-14T03:57:18.2259109Z  issue = get_issue(gh, repo, issue_num) 2025-03-14T03:57:18.2259841Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-03-14T03:57:18.2260657Z  2025-03-14T03:57:18.2261036Z  2025-03-14T03:57:18.2261828Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-03-14T03:57:18.2262674Z  for _ in range(num_retries): 2025-03-14T03:57:18.2263222Z  try: 2025-03-14T03:57:18.2263722Z  req = Request(url=url, headers=headers) 2025-03-14T03:57:18.2264475Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-03-14T03:57:18.2265208Z  return json.loads(content) 2025-03-14T03:57:18.2265870Z  except Exception as e: 2025-03-14T03:57:18.2266518Z  log.warning(f"Could not download {url}: {e}") 2025-03-14T03:57:18.2267147Z  2025-03-14T03:57:18.2267784Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-03-14T03:57:18.2268587Z  return {} 2025-03-14T03:57:18.2269042Z  2025-03-14T03:57:18.2269424Z  2025-03-14T03:57:18.2269807Z @cache 2025-03-14T03:57:18.2270530Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-03-14T03:57:18.2271521Z  """ 2025-03-14T03:57:18.2272029Z  Dynamically get PR information 2025-03-14T03:57:18.2272586Z  """ 2025-03-14T03:57:18.2342800Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-03-14T03:57:18.2344313Z  headers = { 2025-03-14T03:57:18.2345346Z  "Accept": "application/vnd.github.v3+json", 2025-03-14T03:57:18.2346401Z  "Authorization": f"token {github_token}", 2025-03-14T03:57:18.2347004Z  } 2025-03-14T03:57:18.2347732Z  json_response: dict[str, Any] = download_json( 2025-03-14T03:57:18.2348435Z  url=f"{github_api}/issues/{pr_number}", 2025-03-14T03:57:18.2349055Z  headers=headers, 2025-03-14T03:57:18.2349564Z  ) 2025-03-14T03:57:18.2349974Z  2025-03-14T03:57:18.2350380Z  if not json_response: 2025-03-14T03:57:18.2351061Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-03-14T03:57:18.2352085Z  return {} 2025-03-14T03:57:18.2352559Z  2025-03-14T03:57:18.2352963Z  return json_response 2025-03-14T03:57:18.2353471Z  2025-03-14T03:57:18.2353841Z  2025-03-14T03:57:18.2354492Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-03-14T03:57:18.2355276Z  """ 2025-03-14T03:57:18.2355890Z  Dynamically get the latest list of labels from the pull request 2025-03-14T03:57:18.2356606Z  """ 2025-03-14T03:57:18.2357167Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-03-14T03:57:18.2357827Z  return { 2025-03-14T03:57:18.2358502Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-03-14T03:57:18.2359238Z  } 2025-03-14T03:57:18.2359638Z  2025-03-14T03:57:18.2360016Z  2025-03-14T03:57:18.2360405Z def main() -> None: 2025-03-14T03:57:18.2360900Z  args = parse_args() 2025-03-14T03:57:18.2361931Z  2025-03-14T03:57:18.2362446Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-03-14T03:57:18.2363035Z  2025-03-14T03:57:18.2363448Z  # Check if the PR is opt-out 2025-03-14T03:57:18.2364027Z  if args.pr_number: 2025-03-14T03:57:18.2364773Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-03-14T03:57:18.2365599Z  if OPT_OUT_LABEL in labels: 2025-03-14T03:57:18.2366154Z  log.info( 2025-03-14T03:57:18.2366936Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-03-14T03:57:18.2367912Z  ) 2025-03-14T03:57:18.2368562Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:57:18.2369300Z  sys.exit() 2025-03-14T03:57:18.2369786Z  2025-03-14T03:57:18.2370173Z  try: 2025-03-14T03:57:18.2370737Z  rollout_state = get_rollout_state_from_issue( 2025-03-14T03:57:18.2371815Z  args.github_token, args.github_issue_repo, args.github_issue 2025-03-14T03:57:18.2372518Z  ) 2025-03-14T03:57:18.2372938Z  2025-03-14T03:57:18.2373382Z  username = get_potential_pr_author( 2025-03-14T03:57:18.2373986Z  args.github_token, 2025-03-14T03:57:18.2374562Z  args.github_repo, 2025-03-14T03:57:18.2375125Z  args.github_actor, 2025-03-14T03:57:18.2375697Z  args.github_ref_type, 2025-03-14T03:57:18.2376271Z  args.github_branch, 2025-03-14T03:57:18.2376809Z  ) 2025-03-14T03:57:18.2377224Z  2025-03-14T03:57:18.2377765Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-03-14T03:57:18.2378435Z  2025-03-14T03:57:18.2378909Z  runner_label_prefix = get_runner_prefix( 2025-03-14T03:57:18.2379524Z  rollout_state, 2025-03-14T03:57:18.2380099Z  (args.github_issue_owner, username), 2025-03-14T03:57:18.2380707Z  args.github_branch, 2025-03-14T03:57:18.2381292Z  args.eligible_experiments, 2025-03-14T03:57:18.2382152Z  is_canary, 2025-03-14T03:57:18.2382787Z  ) 2025-03-14T03:57:18.2383213Z  2025-03-14T03:57:18.2383622Z  except Exception as e: 2025-03-14T03:57:18.2384144Z  log.error( 2025-03-14T03:57:18.2384916Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-03-14T03:57:18.2385732Z  ) 2025-03-14T03:57:18.2386143Z  2025-03-14T03:57:18.2386715Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:57:18.2387415Z  2025-03-14T03:57:18.2387789Z  2025-03-14T03:57:18.2388189Z if __name__ == "__main__": 2025-03-14T03:57:18.2388700Z  main() 2025-03-14T03:57:18.2389115Z  2025-03-14T03:57:18.2389485Z EOF 2025-03-14T03:57:18.2389869Z  2025-03-14T03:57:18.2390281Z cat runner_determinator.py 2025-03-14T03:57:18.2667726Z shell: /usr/bin/bash -e {0} 2025-03-14T03:57:18.2668556Z env: 2025-03-14T03:57:18.2669319Z GITHUB_TOKEN: *** 2025-03-14T03:57:18.2669764Z ISSUE_NUMBER: 5132 2025-03-14T03:57:18.2670238Z TRIGGERING_ACTOR: pytorchmergebot 2025-03-14T03:57:18.2670772Z ISSUE_OWNER: 2025-03-14T03:57:18.2671197Z CHECK_EXPERIMENTS: 2025-03-14T03:57:18.2671997Z PR_NUMBER: 2025-03-14T03:57:18.2672415Z ##[endgroup] 2025-03-14T03:57:18.2895595Z # flake8: noqa: G004 2025-03-14T03:57:18.2896138Z 2025-03-14T03:57:18.2896755Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-03-14T03:57:18.2897743Z # must be kept in sync. You can do it easily by running the following command: 2025-03-14T03:57:18.2898567Z # python .github/scripts/update_runner_determinator.py 2025-03-14T03:57:18.2899032Z 2025-03-14T03:57:18.2899199Z """ 2025-03-14T03:57:18.2899806Z This runner determinator is used to determine which set of runners to run a 2025-03-14T03:57:18.2900741Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-03-14T03:57:18.2902197Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-03-14T03:57:18.2903079Z of which runners should be used to run which job. 2025-03-14T03:57:18.2903495Z 2025-03-14T03:57:18.2904122Z The configuration has two parts, the settings and a list of opted-in users, 2025-03-14T03:57:18.2905053Z separated by a line containing "---". If the line is not present, the 2025-03-14T03:57:18.2905962Z settings are considered to be empty with only the second part, the user 2025-03-14T03:57:18.2906667Z list, defined. 2025-03-14T03:57:18.2906906Z 2025-03-14T03:57:18.2907285Z The first part is a YAML block that defines the rollout settings. This can be 2025-03-14T03:57:18.2908246Z used to define any settings that are needed to determine which runners to use. 2025-03-14T03:57:18.2909105Z It's fields are defined by the RolloutSettings class below. 2025-03-14T03:57:18.2909557Z 2025-03-14T03:57:18.2909945Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-03-14T03:57:18.2910852Z The user list is also a comma separated list of additional features or 2025-03-14T03:57:18.2911791Z experiments which the user could be opted in to. 2025-03-14T03:57:18.2912221Z 2025-03-14T03:57:18.2912437Z The user list has the following rules: 2025-03-14T03:57:18.2912801Z 2025-03-14T03:57:18.2913123Z - Users are GitHub usernames, which must start with the @ prefix 2025-03-14T03:57:18.2914009Z - Each user is also a comma-separated list of features/experiments to enable 2025-03-14T03:57:18.2914792Z - A "#" prefix opts the user out of all experiments 2025-03-14T03:57:18.2915195Z 2025-03-14T03:57:18.2915377Z Example config: 2025-03-14T03:57:18.2915846Z # A list of experiments that can be opted into. 2025-03-14T03:57:18.2916552Z # This defines the behavior they'll induce when opted into. 2025-03-14T03:57:18.2917200Z # Expected syntax is: 2025-03-14T03:57:18.2917878Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-03-14T03:57:18.2919032Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-03-14T03:57:18.2919675Z 2025-03-14T03:57:18.2919854Z experiments: 2025-03-14T03:57:18.2920267Z lf: 2025-03-14T03:57:18.2920668Z rollout_percent: 25 2025-03-14T03:57:18.2921142Z all_branches: false 2025-03-14T03:57:18.2921935Z default: true 2025-03-14T03:57:18.2922368Z --- 2025-03-14T03:57:18.2922584Z 2025-03-14T03:57:18.2922753Z # Opt-ins: 2025-03-14T03:57:18.2923374Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-03-14T03:57:18.2924274Z # and specifying experiments to enable in a comma-separated list. 2025-03-14T03:57:18.2925081Z # To always opt out of an experiment, prefix it with a "-". 2025-03-14T03:57:18.2925751Z # Experiments should be from the above list. 2025-03-14T03:57:18.2926152Z 2025-03-14T03:57:18.2926338Z @User1,-lf,split_build 2025-03-14T03:57:18.2926795Z @User2,lf 2025-03-14T03:57:18.2927204Z @User3,split_build 2025-03-14T03:57:18.2927626Z """ 2025-03-14T03:57:18.2927828Z 2025-03-14T03:57:18.2927995Z import json 2025-03-14T03:57:18.2928420Z import logging 2025-03-14T03:57:18.2928830Z import os 2025-03-14T03:57:18.2929206Z import random 2025-03-14T03:57:18.2929595Z import re 2025-03-14T03:57:18.2929966Z import sys 2025-03-14T03:57:18.2930379Z from argparse import ArgumentParser 2025-03-14T03:57:18.2930949Z from collections.abc import Iterable 2025-03-14T03:57:18.2931669Z from functools import cache 2025-03-14T03:57:18.2932173Z from logging import LogRecord 2025-03-14T03:57:18.2932703Z from typing import Any, NamedTuple 2025-03-14T03:57:18.2933268Z from urllib.request import Request, urlopen 2025-03-14T03:57:18.2933654Z 2025-03-14T03:57:18.2933836Z import yaml 2025-03-14T03:57:18.2934263Z from github import Auth, Github 2025-03-14T03:57:18.2934775Z from github.Issue import Issue 2025-03-14T03:57:18.2935103Z 2025-03-14T03:57:18.2935111Z 2025-03-14T03:57:18.2935345Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-03-14T03:57:18.2936070Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-03-14T03:57:18.2936977Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-03-14T03:57:18.2937739Z 2025-03-14T03:57:18.2937987Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-03-14T03:57:18.2938587Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-03-14T03:57:18.2939114Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-03-14T03:57:18.2939711Z OPT_OUT_LABEL = "no-runner-experiments" 2025-03-14T03:57:18.2940089Z 2025-03-14T03:57:18.2940297Z SETTING_EXPERIMENTS = "experiments" 2025-03-14T03:57:18.2940642Z 2025-03-14T03:57:18.2940842Z LF_FLEET_EXPERIMENT = "lf" 2025-03-14T03:57:18.2941332Z CANARY_FLEET_SUFFIX = ".c" 2025-03-14T03:57:18.2941825Z 2025-03-14T03:57:18.2941833Z 2025-03-14T03:57:18.2942039Z class Experiment(NamedTuple): 2025-03-14T03:57:18.2942535Z rollout_perc: float = ( 2025-03-14T03:57:18.2943211Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-03-14T03:57:18.2943910Z ) 2025-03-14T03:57:18.2944295Z all_branches: bool = ( 2025-03-14T03:57:18.2944956Z False # If True, the experiment is also enabled on the exception branches 2025-03-14T03:57:18.2945659Z ) 2025-03-14T03:57:18.2946039Z default: bool = ( 2025-03-14T03:57:18.2946646Z True # If True, the experiment is enabled by default for all queries 2025-03-14T03:57:18.2947317Z ) 2025-03-14T03:57:18.2947521Z 2025-03-14T03:57:18.2947716Z # Add more fields as needed 2025-03-14T03:57:18.2948035Z 2025-03-14T03:57:18.2948042Z 2025-03-14T03:57:18.2948236Z class Settings(NamedTuple): 2025-03-14T03:57:18.2948701Z """ 2025-03-14T03:57:18.2949176Z Settings for the experiments that can be opted into. 2025-03-14T03:57:18.2949779Z """ 2025-03-14T03:57:18.2949983Z 2025-03-14T03:57:18.2950206Z experiments: dict[str, Experiment] = {} 2025-03-14T03:57:18.2950585Z 2025-03-14T03:57:18.2950592Z 2025-03-14T03:57:18.2950950Z class ColorFormatter(logging.Formatter): 2025-03-14T03:57:18.2951836Z """Color codes the log messages based on the log level""" 2025-03-14T03:57:18.2952307Z 2025-03-14T03:57:18.2952481Z COLORS = { 2025-03-14T03:57:18.2952907Z "WARNING": "\033[33m", # Yellow 2025-03-14T03:57:18.2953446Z "ERROR": "\033[31m", # Red 2025-03-14T03:57:18.2953959Z "CRITICAL": "\033[31m", # Red 2025-03-14T03:57:18.2954475Z "INFO": "\033[0m", # Reset 2025-03-14T03:57:18.2954986Z "DEBUG": "\033[0m", # Reset 2025-03-14T03:57:18.2955481Z } 2025-03-14T03:57:18.2955684Z 2025-03-14T03:57:18.2955920Z def format(self, record: LogRecord) -> str: 2025-03-14T03:57:18.2956712Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-03-14T03:57:18.2957514Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-03-14T03:57:18.2958119Z return super().format(record) 2025-03-14T03:57:18.2958487Z 2025-03-14T03:57:18.2958494Z 2025-03-14T03:57:18.2958708Z handler = logging.StreamHandler() 2025-03-14T03:57:18.2959452Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-03-14T03:57:18.2960033Z 2025-03-14T03:57:18.2960306Z log = logging.getLogger(os.path.basename(__file__)) 2025-03-14T03:57:18.2960922Z log.addHandler(handler) 2025-03-14T03:57:18.2961557Z log.setLevel(logging.INFO) 2025-03-14T03:57:18.2961908Z 2025-03-14T03:57:18.2961916Z 2025-03-14T03:57:18.2962179Z def set_github_output(key: str, value: str) -> None: 2025-03-14T03:57:18.2962774Z """ 2025-03-14T03:57:18.2963305Z Defines outputs of the github action that invokes this script 2025-03-14T03:57:18.2963962Z """ 2025-03-14T03:57:18.2964345Z if not GITHUB_OUTPUT: 2025-03-14T03:57:18.2965463Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-03-14T03:57:18.2966623Z log.warning( 2025-03-14T03:57:18.2967513Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-03-14T03:57:18.2968467Z ) 2025-03-14T03:57:18.2978178Z print(f"::set-output name={key}::{value}") 2025-03-14T03:57:18.2979036Z return 2025-03-14T03:57:18.2979282Z 2025-03-14T03:57:18.2979489Z with open(GITHUB_OUTPUT, "a") as f: 2025-03-14T03:57:18.2980095Z log.info(f"Setting output: {key}='{value}'") 2025-03-14T03:57:18.2980688Z f.write(f"{key}={value}\n") 2025-03-14T03:57:18.2981029Z 2025-03-14T03:57:18.2981037Z 2025-03-14T03:57:18.2981598Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-03-14T03:57:18.2982336Z return frozenset( 2025-03-14T03:57:18.2982971Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-03-14T03:57:18.2983671Z ) 2025-03-14T03:57:18.2983881Z 2025-03-14T03:57:18.2983888Z 2025-03-14T03:57:18.2984078Z def parse_args() -> Any: 2025-03-14T03:57:18.2984663Z parser = ArgumentParser("Get dynamic rollout settings") 2025-03-14T03:57:18.2985607Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-03-14T03:57:18.2986412Z parser.add_argument( 2025-03-14T03:57:18.2986893Z "--github-issue-repo", 2025-03-14T03:57:18.2987375Z type=str, 2025-03-14T03:57:18.2987808Z required=False, 2025-03-14T03:57:18.2988288Z default="pytorch/test-infra", 2025-03-14T03:57:18.2988853Z help="GitHub repo to get the issue", 2025-03-14T03:57:18.2989384Z ) 2025-03-14T03:57:18.2989779Z parser.add_argument( 2025-03-14T03:57:18.2990254Z "--github-repo", 2025-03-14T03:57:18.2990701Z type=str, 2025-03-14T03:57:18.2991110Z required=True, 2025-03-14T03:57:18.2991711Z help="GitHub repo where CI is running", 2025-03-14T03:57:18.2992261Z ) 2025-03-14T03:57:18.2992646Z parser.add_argument( 2025-03-14T03:57:18.2993288Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-03-14T03:57:18.2994110Z ) 2025-03-14T03:57:18.2994506Z parser.add_argument( 2025-03-14T03:57:18.2995163Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-03-14T03:57:18.2995874Z ) 2025-03-14T03:57:18.2996261Z parser.add_argument( 2025-03-14T03:57:18.2996925Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-03-14T03:57:18.2997643Z ) 2025-03-14T03:57:18.2998028Z parser.add_argument( 2025-03-14T03:57:18.2998716Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-03-14T03:57:18.2999457Z ) 2025-03-14T03:57:18.2999830Z parser.add_argument( 2025-03-14T03:57:18.3000302Z "--github-ref-type", 2025-03-14T03:57:18.3000775Z type=str, 2025-03-14T03:57:18.3001188Z required=True, 2025-03-14T03:57:18.3001933Z help="Current GitHub ref type, branch or tag", 2025-03-14T03:57:18.3002516Z ) 2025-03-14T03:57:18.3002898Z parser.add_argument( 2025-03-14T03:57:18.3003402Z "--eligible-experiments", 2025-03-14T03:57:18.3003940Z type=_str_comma_separated_to_set, 2025-03-14T03:57:18.3004482Z required=False, 2025-03-14T03:57:18.3004930Z default="", 2025-03-14T03:57:18.3005819Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-03-14T03:57:18.3006802Z ) 2025-03-14T03:57:18.3007193Z parser.add_argument( 2025-03-14T03:57:18.3007647Z "--pr-number", 2025-03-14T03:57:18.3008099Z type=str, 2025-03-14T03:57:18.3008519Z required=False, 2025-03-14T03:57:18.3008959Z default="", 2025-03-14T03:57:18.3009452Z help="the optional PR number where this is run", 2025-03-14T03:57:18.3010024Z ) 2025-03-14T03:57:18.3010231Z 2025-03-14T03:57:18.3010428Z return parser.parse_args() 2025-03-14T03:57:18.3010757Z 2025-03-14T03:57:18.3010765Z 2025-03-14T03:57:18.3011197Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-03-14T03:57:18.3012144Z auth = Auth.Token(github_token) 2025-03-14T03:57:18.3012677Z return Github(auth=auth) 2025-03-14T03:57:18.3012985Z 2025-03-14T03:57:18.3013134Z 2025-03-14T03:57:18.3013641Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-03-14T03:57:18.3014472Z repo = gh.get_repo(repo) 2025-03-14T03:57:18.3015001Z return repo.get_issue(number=issue_num) 2025-03-14T03:57:18.3015389Z 2025-03-14T03:57:18.3015396Z 2025-03-14T03:57:18.3015591Z def get_potential_pr_author( 2025-03-14T03:57:18.3016258Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-03-14T03:57:18.3016965Z ) -> str: 2025-03-14T03:57:18.3017513Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-03-14T03:57:18.3018346Z # Fetch the actual username from the original PR. The PR number is 2025-03-14T03:57:18.3019124Z # embedded in the tag name: ciflow// 2025-03-14T03:57:18.3019563Z 2025-03-14T03:57:18.3019760Z gh = get_gh_client(github_token) 2025-03-14T03:57:18.3020112Z 2025-03-14T03:57:18.3020389Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-03-14T03:57:18.3021040Z split_tag = ref_name.split("/") 2025-03-14T03:57:18.3021680Z if ( 2025-03-14T03:57:18.3022080Z len(split_tag) == 3 2025-03-14T03:57:18.3022581Z and split_tag[0] == "ciflow" 2025-03-14T03:57:18.3023183Z and split_tag[2].isnumeric() 2025-03-14T03:57:18.3023752Z ): 2025-03-14T03:57:18.3024200Z pr_number = split_tag[2] 2025-03-14T03:57:18.3024704Z try: 2025-03-14T03:57:18.3025152Z repository = gh.get_repo(repo) 2025-03-14T03:57:18.3025791Z pull = repository.get_pull(number=int(pr_number)) 2025-03-14T03:57:18.3026420Z except Exception as e: 2025-03-14T03:57:18.3026949Z raise Exception( # noqa: TRY002 2025-03-14T03:57:18.3027768Z f"issue with pull request {pr_number} from repo {repository}" 2025-03-14T03:57:18.3028484Z ) from e 2025-03-14T03:57:18.3029047Z return pull.user.login # type: ignore[no-any-return] 2025-03-14T03:57:18.3029781Z # In all other cases, return the original input username 2025-03-14T03:57:18.3030397Z return username 2025-03-14T03:57:18.3030650Z 2025-03-14T03:57:18.3030657Z 2025-03-14T03:57:18.3030889Z def is_exception_branch(branch: str) -> bool: 2025-03-14T03:57:18.3031601Z """ 2025-03-14T03:57:18.3032280Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-03-14T03:57:18.3033080Z """ 2025-03-14T03:57:18.3033649Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-03-14T03:57:18.3034189Z 2025-03-14T03:57:18.3034197Z 2025-03-14T03:57:18.3034404Z def load_yaml(yaml_text: str) -> Any: 2025-03-14T03:57:18.3034914Z try: 2025-03-14T03:57:18.3035325Z data = yaml.safe_load(yaml_text) 2025-03-14T03:57:18.3035851Z return data 2025-03-14T03:57:18.3036278Z except yaml.YAMLError: 2025-03-14T03:57:18.3036791Z log.exception("Error loading YAML") 2025-03-14T03:57:18.3037328Z raise 2025-03-14T03:57:18.3037556Z 2025-03-14T03:57:18.3037563Z 2025-03-14T03:57:18.3037995Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-03-14T03:57:18.3038761Z """ 2025-03-14T03:57:18.3039410Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-03-14T03:57:18.3040029Z 2025-03-14T03:57:18.3040393Z If the issue body contains "---" then the text above that is the settings 2025-03-14T03:57:18.3041163Z and the text below is the list of opted in users. 2025-03-14T03:57:18.3041693Z 2025-03-14T03:57:18.3042085Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-03-14T03:57:18.3042808Z """ 2025-03-14T03:57:18.3043273Z rollout_state_parts = rollout_state.split("---") 2025-03-14T03:57:18.3043895Z if len(rollout_state_parts) >= 2: 2025-03-14T03:57:18.3044516Z return rollout_state_parts[0], rollout_state_parts[1] 2025-03-14T03:57:18.3045262Z else: 2025-03-14T03:57:18.3045654Z return "", rollout_state 2025-03-14T03:57:18.3045973Z 2025-03-14T03:57:18.3045980Z 2025-03-14T03:57:18.3046186Z class UserOptins(dict[str, list[str]]): 2025-03-14T03:57:18.3046707Z """ 2025-03-14T03:57:18.3047246Z Dictionary of users with a list of features they have opted into 2025-03-14T03:57:18.3047911Z """ 2025-03-14T03:57:18.3048125Z 2025-03-14T03:57:18.3048132Z 2025-03-14T03:57:18.3048487Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-03-14T03:57:18.3049165Z """ 2025-03-14T03:57:18.3049908Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-03-14T03:57:18.3050624Z 2025-03-14T03:57:18.3051291Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-03-14T03:57:18.3052464Z - Example line: "@User1,lf,split_build" 2025-03-14T03:57:18.3053188Z - A "#" prefix indicates the user is opted out of all experiments 2025-03-14T03:57:18.3053701Z 2025-03-14T03:57:18.3053708Z 2025-03-14T03:57:18.3053873Z """ 2025-03-14T03:57:18.3054274Z optins = UserOptins() 2025-03-14T03:57:18.3054785Z for user in user_optin_text.split("\n"): 2025-03-14T03:57:18.3055362Z user = user.strip("\r\n\t -") 2025-03-14T03:57:18.3055928Z if not user or not user.startswith("@"): 2025-03-14T03:57:18.3056503Z # Not a valid user. Skip 2025-03-14T03:57:18.3057017Z continue 2025-03-14T03:57:18.3057277Z 2025-03-14T03:57:18.3057442Z if user: 2025-03-14T03:57:18.3057899Z usr_name = user.split(",")[0].strip("@") 2025-03-14T03:57:18.3058629Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-03-14T03:57:18.3059272Z 2025-03-14T03:57:18.3059458Z return optins 2025-03-14T03:57:18.3059715Z 2025-03-14T03:57:18.3059722Z 2025-03-14T03:57:18.3060023Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-03-14T03:57:18.3060656Z """ 2025-03-14T03:57:18.3061070Z Check if the experiment name is valid. 2025-03-14T03:57:18.3061720Z A valid name: 2025-03-14T03:57:18.3062396Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-03-14T03:57:18.3063364Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-03-14T03:57:18.3064108Z - Cannot contain spaces 2025-03-14T03:57:18.3064585Z """ 2025-03-14T03:57:18.3064791Z 2025-03-14T03:57:18.3065073Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-03-14T03:57:18.3065812Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-03-14T03:57:18.3066282Z 2025-03-14T03:57:18.3066452Z if valid: 2025-03-14T03:57:18.3066858Z return True 2025-03-14T03:57:18.3067112Z 2025-03-14T03:57:18.3067279Z log.error( 2025-03-14T03:57:18.3068804Z 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-03-14T03:57:18.3070431Z ) 2025-03-14T03:57:18.3070804Z return False 2025-03-14T03:57:18.3071048Z 2025-03-14T03:57:18.3071055Z 2025-03-14T03:57:18.3071466Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-03-14T03:57:18.3072121Z """ 2025-03-14T03:57:18.3072738Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-03-14T03:57:18.3073484Z """ 2025-03-14T03:57:18.3073850Z try: 2025-03-14T03:57:18.3074232Z if settings_text: 2025-03-14T03:57:18.3074985Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-03-14T03:57:18.3075814Z # for easy reading 2025-03-14T03:57:18.3076632Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-03-14T03:57:18.3077712Z # the backtick character in shell commands. 2025-03-14T03:57:18.3078340Z backtick = chr(96) # backtick character 2025-03-14T03:57:18.3079023Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-03-14T03:57:18.3079716Z settings = load_yaml(settings_text) 2025-03-14T03:57:18.3080106Z 2025-03-14T03:57:18.3080529Z # For now we just load experiments. We can expand this if/when we add more settings 2025-03-14T03:57:18.3081314Z experiments = {} 2025-03-14T03:57:18.3081730Z 2025-03-14T03:57:18.3082108Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-03-14T03:57:18.3082889Z if not is_valid_experiment_name(exp_name): 2025-03-14T03:57:18.3084038Z # 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-03-14T03:57:18.3085119Z continue 2025-03-14T03:57:18.3085422Z 2025-03-14T03:57:18.3085609Z valid_settings = {} 2025-03-14T03:57:18.3086156Z for setting in exp_settings: 2025-03-14T03:57:18.3086745Z if setting not in Experiment._fields: 2025-03-14T03:57:18.3087325Z log.warning( 2025-03-14T03:57:18.3088059Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-03-14T03:57:18.3088807Z ) 2025-03-14T03:57:18.3089254Z else: 2025-03-14T03:57:18.3089787Z valid_settings[setting] = exp_settings[setting] 2025-03-14T03:57:18.3090244Z 2025-03-14T03:57:18.3090545Z experiments[exp_name] = Experiment(**valid_settings) 2025-03-14T03:57:18.3091347Z return Settings(experiments) 2025-03-14T03:57:18.3091845Z 2025-03-14T03:57:18.3092033Z except Exception: 2025-03-14T03:57:18.3092537Z log.exception("Failed to parse settings") 2025-03-14T03:57:18.3092947Z 2025-03-14T03:57:18.3093133Z return Settings() 2025-03-14T03:57:18.3093396Z 2025-03-14T03:57:18.3093404Z 2025-03-14T03:57:18.3093668Z def parse_settings(rollout_state: str) -> Settings: 2025-03-14T03:57:18.3094264Z """ 2025-03-14T03:57:18.3094709Z Parse settings, if any, from the rollout state. 2025-03-14T03:57:18.3095138Z 2025-03-14T03:57:18.3095505Z If the issue body contains "---" then the text above that is the settings 2025-03-14T03:57:18.3096286Z and the text below is the list of opted in users. 2025-03-14T03:57:18.3096709Z 2025-03-14T03:57:18.3097155Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-03-14T03:57:18.3097925Z """ 2025-03-14T03:57:18.3098517Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:57:18.3099300Z return parse_settings_from_text(settings_text) 2025-03-14T03:57:18.3099723Z 2025-03-14T03:57:18.3099729Z 2025-03-14T03:57:18.3099993Z def parse_users(rollout_state: str) -> UserOptins: 2025-03-14T03:57:18.3100575Z """ 2025-03-14T03:57:18.3100976Z Parse users from the rollout state. 2025-03-14T03:57:18.3101344Z 2025-03-14T03:57:18.3101609Z """ 2025-03-14T03:57:18.3102182Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:57:18.3102953Z return parse_user_opt_in_from_text(users_text) 2025-03-14T03:57:18.3103372Z 2025-03-14T03:57:18.3103379Z 2025-03-14T03:57:18.3103814Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:57:18.3104590Z """ 2025-03-14T03:57:18.3105022Z Check if a user is opted into an experiment 2025-03-14T03:57:18.3105583Z """ 2025-03-14T03:57:18.3106044Z return experiment_name in user_optins.get(user, []) 2025-03-14T03:57:18.3106513Z 2025-03-14T03:57:18.3106520Z 2025-03-14T03:57:18.3106959Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:57:18.3107873Z """ 2025-03-14T03:57:18.3108356Z Check if a user explicitly opted out of an experiment 2025-03-14T03:57:18.3108955Z """ 2025-03-14T03:57:18.3109488Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-03-14T03:57:18.3110211Z experiment_optout = "-" + experiment_name 2025-03-14T03:57:18.3110892Z if experiment_optout not in user_optins.get(user, []): 2025-03-14T03:57:18.3111622Z return False 2025-03-14T03:57:18.3111888Z 2025-03-14T03:57:18.3112174Z if is_user_opted_in(user, user_optins, experiment_name): 2025-03-14T03:57:18.3112801Z log.warning( 2025-03-14T03:57:18.3113638Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-03-14T03:57:18.3114557Z ) 2025-03-14T03:57:18.3114786Z 2025-03-14T03:57:18.3114956Z return True 2025-03-14T03:57:18.3115194Z 2025-03-14T03:57:18.3115202Z 2025-03-14T03:57:18.3115390Z def get_runner_prefix( 2025-03-14T03:57:18.3115858Z rollout_state: str, 2025-03-14T03:57:18.3116337Z workflow_requestors: Iterable[str], 2025-03-14T03:57:18.3116886Z branch: str, 2025-03-14T03:57:18.3117395Z eligible_experiments: frozenset[str] = frozenset(), 2025-03-14T03:57:18.3118022Z is_canary: bool = False, 2025-03-14T03:57:18.3118496Z ) -> str: 2025-03-14T03:57:18.3118922Z settings = parse_settings(rollout_state) 2025-03-14T03:57:18.3119522Z user_optins = parse_users(rollout_state) 2025-03-14T03:57:18.3119914Z 2025-03-14T03:57:18.3120094Z fleet_prefix = "" 2025-03-14T03:57:18.3120535Z prefixes = [] 2025-03-14T03:57:18.3121193Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-03-14T03:57:18.3122268Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-03-14T03:57:18.3123126Z log.info( 2025-03-14T03:57:18.3123850Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-03-14T03:57:18.3124639Z ) 2025-03-14T03:57:18.3125032Z continue 2025-03-14T03:57:18.3125284Z 2025-03-14T03:57:18.3125484Z if eligible_experiments: 2025-03-14T03:57:18.3126065Z if experiment_name not in eligible_experiments: 2025-03-14T03:57:18.3126734Z exp_list = ", ".join(eligible_experiments) 2025-03-14T03:57:18.3127314Z log.info( 2025-03-14T03:57:18.3128152Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-03-14T03:57:18.3129069Z ) 2025-03-14T03:57:18.3129476Z continue 2025-03-14T03:57:18.3129967Z elif not experiment_settings.default: 2025-03-14T03:57:18.3130525Z log.info( 2025-03-14T03:57:18.3131216Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-03-14T03:57:18.3132097Z ) 2025-03-14T03:57:18.3132485Z continue 2025-03-14T03:57:18.3132746Z 2025-03-14T03:57:18.3133049Z # Is any workflow_requestor opted out to this experiment? 2025-03-14T03:57:18.3133691Z opted_out_users = [ 2025-03-14T03:57:18.3134155Z requestor 2025-03-14T03:57:18.3134625Z for requestor in workflow_requestors 2025-03-14T03:57:18.3135321Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-03-14T03:57:18.3135973Z ] 2025-03-14T03:57:18.3136194Z 2025-03-14T03:57:18.3136377Z if opted_out_users: 2025-03-14T03:57:18.3136843Z log.info( 2025-03-14T03:57:18.3137487Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-03-14T03:57:18.3138220Z ) 2025-03-14T03:57:18.3138602Z continue 2025-03-14T03:57:18.3138863Z 2025-03-14T03:57:18.3139150Z # Is any workflow_requestor opted in to this experiment? 2025-03-14T03:57:18.3139789Z opted_in_users = [ 2025-03-14T03:57:18.3140248Z requestor 2025-03-14T03:57:18.3140861Z for requestor in workflow_requestors 2025-03-14T03:57:18.3141662Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-03-14T03:57:18.3142331Z ] 2025-03-14T03:57:18.3142553Z 2025-03-14T03:57:18.3142734Z enabled = False 2025-03-14T03:57:18.3143191Z if opted_in_users: 2025-03-14T03:57:18.3143658Z log.info( 2025-03-14T03:57:18.3144290Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-03-14T03:57:18.3145015Z ) 2025-03-14T03:57:18.3145431Z enabled = True 2025-03-14T03:57:18.3145724Z 2025-03-14T03:57:18.3145962Z elif experiment_settings.rollout_perc: 2025-03-14T03:57:18.3146857Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-03-14T03:57:18.3147841Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-03-14T03:57:18.3148528Z log.info( 2025-03-14T03:57:18.3149457Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-03-14T03:57:18.3150453Z ) 2025-03-14T03:57:18.3150872Z enabled = True 2025-03-14T03:57:18.3151186Z 2025-03-14T03:57:18.3151360Z if enabled: 2025-03-14T03:57:18.3151902Z label = experiment_name 2025-03-14T03:57:18.3152479Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-03-14T03:57:18.3153349Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-03-14T03:57:18.3154272Z # - If it's enabled, then we always list it's prefix first 2025-03-14T03:57:18.3155181Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-03-14T03:57:18.3155883Z if is_canary: 2025-03-14T03:57:18.3156386Z label += CANARY_FLEET_SUFFIX 2025-03-14T03:57:18.3156964Z fleet_prefix = label 2025-03-14T03:57:18.3157483Z else: 2025-03-14T03:57:18.3157937Z prefixes.append(label) 2025-03-14T03:57:18.3158310Z 2025-03-14T03:57:18.3158504Z if len(prefixes) > 1: 2025-03-14T03:57:18.3158966Z log.error( 2025-03-14T03:57:18.3160056Z 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-03-14T03:57:18.3161231Z ) 2025-03-14T03:57:18.3161751Z prefixes = prefixes[:1] 2025-03-14T03:57:18.3162077Z 2025-03-14T03:57:18.3162276Z # Fleet always comes first 2025-03-14T03:57:18.3162786Z if fleet_prefix: 2025-03-14T03:57:18.3163251Z prefixes.insert(0, fleet_prefix) 2025-03-14T03:57:18.3163628Z 2025-03-14T03:57:18.3163895Z return ".".join(prefixes) + "." if prefixes else "" 2025-03-14T03:57:18.3164332Z 2025-03-14T03:57:18.3164340Z 2025-03-14T03:57:18.3164804Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-03-14T03:57:18.3165618Z """ 2025-03-14T03:57:18.3166225Z Gets the first comment of the issue, which contains the desired rollout state. 2025-03-14T03:57:18.3166812Z 2025-03-14T03:57:18.3167213Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-03-14T03:57:18.3167948Z """ 2025-03-14T03:57:18.3168348Z gh = get_gh_client(github_token) 2025-03-14T03:57:18.3168912Z issue = get_issue(gh, repo, issue_num) 2025-03-14T03:57:18.3169575Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-03-14T03:57:18.3170040Z 2025-03-14T03:57:18.3170047Z 2025-03-14T03:57:18.3170471Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-03-14T03:57:18.3171261Z for _ in range(num_retries): 2025-03-14T03:57:18.3171860Z try: 2025-03-14T03:57:18.3172306Z req = Request(url=url, headers=headers) 2025-03-14T03:57:18.3173004Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-03-14T03:57:18.3173822Z return json.loads(content) 2025-03-14T03:57:18.3174365Z except Exception as e: 2025-03-14T03:57:18.3174932Z log.warning(f"Could not download {url}: {e}") 2025-03-14T03:57:18.3175360Z 2025-03-14T03:57:18.3175757Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-03-14T03:57:18.3176494Z return {} 2025-03-14T03:57:18.3176725Z 2025-03-14T03:57:18.3176732Z 2025-03-14T03:57:18.3176899Z @cache 2025-03-14T03:57:18.3177546Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-03-14T03:57:18.3178318Z """ 2025-03-14T03:57:18.3178729Z Dynamically get PR information 2025-03-14T03:57:18.3179231Z """ 2025-03-14T03:57:18.3179747Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-03-14T03:57:18.3180410Z headers = { 2025-03-14T03:57:18.3180875Z "Accept": "application/vnd.github.v3+json", 2025-03-14T03:57:18.3181612Z "Authorization": f"token {github_token}", 2025-03-14T03:57:18.3182187Z } 2025-03-14T03:57:18.3182625Z json_response: dict[str, Any] = download_json( 2025-03-14T03:57:18.3183254Z url=f"{github_api}/issues/{pr_number}", 2025-03-14T03:57:18.3183817Z headers=headers, 2025-03-14T03:57:18.3184254Z ) 2025-03-14T03:57:18.3184469Z 2025-03-14T03:57:18.3184656Z if not json_response: 2025-03-14T03:57:18.3185241Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-03-14T03:57:18.3185877Z return {} 2025-03-14T03:57:18.3186115Z 2025-03-14T03:57:18.3186297Z return json_response 2025-03-14T03:57:18.3186586Z 2025-03-14T03:57:18.3186593Z 2025-03-14T03:57:18.3187012Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-03-14T03:57:18.3187894Z """ 2025-03-14T03:57:18.3188459Z Dynamically get the latest list of labels from the pull request 2025-03-14T03:57:18.3189132Z """ 2025-03-14T03:57:18.3189628Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-03-14T03:57:18.3190267Z return { 2025-03-14T03:57:18.3190868Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-03-14T03:57:18.3191696Z } 2025-03-14T03:57:18.3191903Z 2025-03-14T03:57:18.3191910Z 2025-03-14T03:57:18.3192095Z def main() -> None: 2025-03-14T03:57:18.3192531Z args = parse_args() 2025-03-14T03:57:18.3192807Z 2025-03-14T03:57:18.3193033Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-03-14T03:57:18.3193436Z 2025-03-14T03:57:18.3193635Z # Check if the PR is opt-out 2025-03-14T03:57:18.3194140Z if args.pr_number: 2025-03-14T03:57:18.3194821Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-03-14T03:57:18.3195605Z if OPT_OUT_LABEL in labels: 2025-03-14T03:57:18.3196109Z log.info( 2025-03-14T03:57:18.3196819Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-03-14T03:57:18.3197615Z ) 2025-03-14T03:57:18.3198182Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:57:18.3198872Z sys.exit() 2025-03-14T03:57:18.3199138Z 2025-03-14T03:57:18.3199302Z try: 2025-03-14T03:57:18.3199750Z rollout_state = get_rollout_state_from_issue( 2025-03-14T03:57:18.3200489Z args.github_token, args.github_issue_repo, args.github_issue 2025-03-14T03:57:18.3201144Z ) 2025-03-14T03:57:18.3201357Z 2025-03-14T03:57:18.3201674Z username = get_potential_pr_author( 2025-03-14T03:57:18.3202237Z args.github_token, 2025-03-14T03:57:18.3202725Z args.github_repo, 2025-03-14T03:57:18.3203224Z args.github_actor, 2025-03-14T03:57:18.3203722Z args.github_ref_type, 2025-03-14T03:57:18.3204238Z args.github_branch, 2025-03-14T03:57:18.3204708Z ) 2025-03-14T03:57:18.3204920Z 2025-03-14T03:57:18.3205214Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-03-14T03:57:18.3205860Z 2025-03-14T03:57:18.3206085Z runner_label_prefix = get_runner_prefix( 2025-03-14T03:57:18.3206660Z rollout_state, 2025-03-14T03:57:18.3207160Z (args.github_issue_owner, username), 2025-03-14T03:57:18.3207727Z args.github_branch, 2025-03-14T03:57:18.3208242Z args.eligible_experiments, 2025-03-14T03:57:18.3208779Z is_canary, 2025-03-14T03:57:18.3209204Z ) 2025-03-14T03:57:18.3209418Z 2025-03-14T03:57:18.3209613Z except Exception as e: 2025-03-14T03:57:18.3210084Z log.error( 2025-03-14T03:57:18.3210775Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-03-14T03:57:18.3211669Z ) 2025-03-14T03:57:18.3211887Z 2025-03-14T03:57:18.3212239Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:57:18.3212760Z 2025-03-14T03:57:18.3212767Z 2025-03-14T03:57:18.3212945Z if __name__ == "__main__": 2025-03-14T03:57:18.3213411Z main() 2025-03-14T03:57:18.3213623Z 2025-03-14T03:57:18.3300369Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-03-14T03:57:18.3301293Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-03-14T03:57:18.3346639Z shell: /usr/bin/bash -e {0} 2025-03-14T03:57:18.3347130Z env: 2025-03-14T03:57:18.3347756Z GITHUB_TOKEN: *** 2025-03-14T03:57:18.3348184Z ISSUE_NUMBER: 5132 2025-03-14T03:57:18.3348645Z TRIGGERING_ACTOR: pytorchmergebot 2025-03-14T03:57:18.3349164Z ISSUE_OWNER: 2025-03-14T03:57:18.3349567Z CHECK_EXPERIMENTS: 2025-03-14T03:57:18.3349996Z PR_NUMBER: 2025-03-14T03:57:18.3350394Z ##[endgroup] 2025-03-14T03:57:18.6679172Z Defaulting to user installation because normal site-packages is not writeable 2025-03-14T03:57:18.9259128Z Collecting urllib3==1.26.18 2025-03-14T03:57:18.9632996Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-03-14T03:57:18.9856399Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.6 MB/s eta 0:00:00 2025-03-14T03:57:19.0063513Z Collecting PyGithub==2.3.0 2025-03-14T03:57:19.0087170Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-03-14T03:57:19.0489003Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-03-14T03:57:19.0517377Z 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-03-14T03:57:19.0565190Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-03-14T03:57:19.0581761Z 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-03-14T03:57:19.0596449Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-03-14T03:57:19.0826519Z Collecting Deprecated (from PyGithub==2.3.0) 2025-03-14T03:57:19.0850644Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-03-14T03:57:19.1075864Z 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-03-14T03:57:19.2186736Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-03-14T03:57:19.2213047Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-03-14T03:57:19.3399481Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-03-14T03:57:19.3428428Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.4 kB) 2025-03-14T03:57:19.3593672Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-03-14T03:57:19.3617114Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-03-14T03:57:19.3834297Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-03-14T03:57:19.3889356Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 37.0 MB/s eta 0:00:00 2025-03-14T03:57:19.3915949Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-03-14T03:57:19.3986903Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 76.0 MB/s eta 0:00:00 2025-03-14T03:57:19.4018600Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-03-14T03:57:19.4147856Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 79.2 MB/s eta 0:00:00 2025-03-14T03:57:19.4180628Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-03-14T03:57:19.4245133Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-03-14T03:57:19.4336859Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 69.3 MB/s eta 0:00:00 2025-03-14T03:57:19.4369725Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (89 kB) 2025-03-14T03:57:19.4424841Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 24.7 MB/s eta 0:00:00 2025-03-14T03:57:19.4462586Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-03-14T03:57:19.4522623Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 30.3 MB/s eta 0:00:00 2025-03-14T03:57:19.7355766Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-03-14T03:57:20.2611155Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.2 2025-03-14T03:57:20.3344636Z ##[group]Run curr_branch="main" 2025-03-14T03:57:20.3345033Z curr_branch="main" 2025-03-14T03:57:20.3345308Z curr_ref_type="branch" 2025-03-14T03:57:20.3345625Z echo "Current branch is '$curr_branch'" 2025-03-14T03:57:20.3345944Z  2025-03-14T03:57:20.3346192Z python3 runner_determinator.py \ 2025-03-14T03:57:20.3346527Z  --github-token "$GITHUB_TOKEN" \ 2025-03-14T03:57:20.3346859Z  --github-issue "$ISSUE_NUMBER" \ 2025-03-14T03:57:20.3347218Z  --github-branch "$curr_branch" \ 2025-03-14T03:57:20.3347541Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-03-14T03:57:20.3347878Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-03-14T03:57:20.3348229Z  --github-ref-type "$curr_ref_type" \ 2025-03-14T03:57:20.3348571Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-03-14T03:57:20.3348954Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-03-14T03:57:20.3349342Z  --pr-number "${PR_NUMBER}" 2025-03-14T03:57:20.3398945Z shell: /usr/bin/bash -e {0} 2025-03-14T03:57:20.3399231Z env: 2025-03-14T03:57:20.3399892Z GITHUB_TOKEN: *** 2025-03-14T03:57:20.3400144Z ISSUE_NUMBER: 5132 2025-03-14T03:57:20.3400398Z TRIGGERING_ACTOR: pytorchmergebot 2025-03-14T03:57:20.3400685Z ISSUE_OWNER: 2025-03-14T03:57:20.3400927Z CHECK_EXPERIMENTS: 2025-03-14T03:57:20.3401170Z PR_NUMBER: 2025-03-14T03:57:20.3401813Z ##[endgroup] 2025-03-14T03:57:20.3469487Z Current branch is 'main' 2025-03-14T03:57:21.5672483Z INFO : Based on rollout percentage of 50%, enabling experiment lf. 2025-03-14T03:57:21.5673607Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-03-14T03:57:21.5674978Z INFO : Setting output: label-type='lf.' 2025-03-14T03:57:21.5979523Z Evaluate and set job outputs 2025-03-14T03:57:21.5986268Z Set output 'label-type' 2025-03-14T03:57:21.5988075Z Cleaning up orphan processes