2025-03-14T03:51:43.1400156Z Current runner version: '2.322.0' 2025-03-14T03:51:43.1428300Z ##[group]Operating System 2025-03-14T03:51:43.1429107Z Ubuntu 2025-03-14T03:51:43.1429624Z 24.04.2 2025-03-14T03:51:43.1430173Z LTS 2025-03-14T03:51:43.1430642Z ##[endgroup] 2025-03-14T03:51:43.1431140Z ##[group]Runner Image 2025-03-14T03:51:43.1431759Z Image: ubuntu-24.04 2025-03-14T03:51:43.1432279Z Version: 20250309.1.0 2025-03-14T03:51:43.1433313Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250309.1/images/ubuntu/Ubuntu2404-Readme.md 2025-03-14T03:51:43.1434764Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250309.1 2025-03-14T03:51:43.1435693Z ##[endgroup] 2025-03-14T03:51:43.1436154Z ##[group]Runner Image Provisioner 2025-03-14T03:51:43.1437122Z 2.0.422.1 2025-03-14T03:51:43.1437626Z ##[endgroup] 2025-03-14T03:51:43.1438691Z ##[group]GITHUB_TOKEN Permissions 2025-03-14T03:51:43.1440721Z Contents: read 2025-03-14T03:51:43.1441263Z Metadata: read 2025-03-14T03:51:43.1442065Z ##[endgroup] 2025-03-14T03:51:43.1445309Z Secret source: Actions 2025-03-14T03:51:43.1446358Z Prepare workflow directory 2025-03-14T03:51:43.1946685Z Prepare all required actions 2025-03-14T03:51:43.2001739Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (aed0b7a742a2d7b7901790622829cbd2135049a4) 2025-03-14T03:51:43.2007338Z ##[group] Inputs 2025-03-14T03:51:43.2007953Z check_experiments: 2025-03-14T03:51:43.2008654Z triggering_actor: pytorchmergebot 2025-03-14T03:51:43.2009271Z issue_owner: 2025-03-14T03:51:43.2009746Z curr_branch: main 2025-03-14T03:51:43.2010350Z curr_ref_type: branch 2025-03-14T03:51:43.2010889Z issue_number: 5132 2025-03-14T03:51:43.2011401Z ##[endgroup] 2025-03-14T03:51:43.2012155Z Complete job name: before-test / get-label-type / runner-determinator 2025-03-14T03:51:43.8416818Z ##[group]Run cat < runner_determinator.py 2025-03-14T03:51:43.8418624Z cat < runner_determinator.py 2025-03-14T03:51:43.8419254Z # flake8: noqa: G004 2025-03-14T03:51:43.8419788Z  2025-03-14T03:51:43.8420535Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-03-14T03:51:43.8421624Z # must be kept in sync. You can do it easily by running the following command: 2025-03-14T03:51:43.8422558Z # python .github/scripts/update_runner_determinator.py 2025-03-14T03:51:43.8423249Z  2025-03-14T03:51:43.8423640Z """ 2025-03-14T03:51:43.8424324Z This runner determinator is used to determine which set of runners to run a 2025-03-14T03:51:43.8425348Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-03-14T03:51:43.8426614Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-03-14T03:51:43.8427615Z of which runners should be used to run which job. 2025-03-14T03:51:43.8428269Z  2025-03-14T03:51:43.8428938Z The configuration has two parts, the settings and a list of opted-in users, 2025-03-14T03:51:43.8430008Z separated by a line containing "---". If the line is not present, the 2025-03-14T03:51:43.8431038Z settings are considered to be empty with only the second part, the user 2025-03-14T03:51:43.8431848Z list, defined. 2025-03-14T03:51:43.8432309Z  2025-03-14T03:51:43.8432955Z The first part is a YAML block that defines the rollout settings. This can be 2025-03-14T03:51:43.8434023Z used to define any settings that are needed to determine which runners to use. 2025-03-14T03:51:43.8434997Z It's fields are defined by the RolloutSettings class below. 2025-03-14T03:51:43.8435692Z  2025-03-14T03:51:43.8436362Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-03-14T03:51:43.8437505Z The user list is also a comma separated list of additional features or 2025-03-14T03:51:43.8438635Z experiments which the user could be opted in to. 2025-03-14T03:51:43.8439298Z  2025-03-14T03:51:43.8439763Z The user list has the following rules: 2025-03-14T03:51:43.8440371Z  2025-03-14T03:51:43.8440987Z - Users are GitHub usernames, which must start with the @ prefix 2025-03-14T03:51:43.8441984Z - Each user is also a comma-separated list of features/experiments to enable 2025-03-14T03:51:43.8442898Z - A "#" prefix opts the user out of all experiments 2025-03-14T03:51:43.8443544Z  2025-03-14T03:51:43.8444080Z Example config: 2025-03-14T03:51:43.8444680Z  # A list of experiments that can be opted into. 2025-03-14T03:51:43.8445482Z  # This defines the behavior they'll induce when opted into. 2025-03-14T03:51:43.8446233Z  # Expected syntax is: 2025-03-14T03:51:43.8447151Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-03-14T03:51:43.8448291Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-03-14T03:51:43.8449162Z  2025-03-14T03:51:43.8449578Z  experiments: 2025-03-14T03:51:43.8450074Z  lf: 2025-03-14T03:51:43.8450536Z  rollout_percent: 25 2025-03-14T03:51:43.8451115Z  all_branches: false 2025-03-14T03:51:43.8451670Z  default: true 2025-03-14T03:51:43.8452185Z  --- 2025-03-14T03:51:43.8452618Z  2025-03-14T03:51:43.8453019Z  # Opt-ins: 2025-03-14T03:51:43.8453750Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-03-14T03:51:43.8454931Z  # and specifying experiments to enable in a comma-separated list. 2025-03-14T03:51:43.8455881Z  # To always opt out of an experiment, prefix it with a "-". 2025-03-14T03:51:43.8456986Z  # Experiments should be from the above list. 2025-03-14T03:51:43.8457646Z  2025-03-14T03:51:43.8458066Z  @User1,-lf,split_build 2025-03-14T03:51:43.8458626Z  @User2,lf 2025-03-14T03:51:43.8459148Z  @User3,split_build 2025-03-14T03:51:43.8459686Z """ 2025-03-14T03:51:43.8460098Z  2025-03-14T03:51:43.8460514Z import json 2025-03-14T03:51:43.8460989Z import logging 2025-03-14T03:51:43.8461474Z import os 2025-03-14T03:51:43.8461931Z import random 2025-03-14T03:51:43.8462405Z import re 2025-03-14T03:51:43.8462863Z import sys 2025-03-14T03:51:43.8463383Z from argparse import ArgumentParser 2025-03-14T03:51:43.8464036Z from collections.abc import Iterable 2025-03-14T03:51:43.8464672Z from functools import cache 2025-03-14T03:51:43.8465266Z from logging import LogRecord 2025-03-14T03:51:43.8465883Z from typing import Any, NamedTuple 2025-03-14T03:51:43.8466664Z from urllib.request import Request, urlopen 2025-03-14T03:51:43.8467298Z  2025-03-14T03:51:43.8467710Z import yaml 2025-03-14T03:51:43.8468205Z from github import Auth, Github 2025-03-14T03:51:43.8468809Z from github.Issue import Issue 2025-03-14T03:51:43.8469377Z  2025-03-14T03:51:43.8469782Z  2025-03-14T03:51:43.8470269Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-03-14T03:51:43.8471102Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-03-14T03:51:43.8472115Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-03-14T03:51:43.8472943Z  2025-03-14T03:51:43.8473455Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-03-14T03:51:43.8474139Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-03-14T03:51:43.8474781Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-03-14T03:51:43.8475631Z OPT_OUT_LABEL = "no-runner-experiments" 2025-03-14T03:51:43.8476234Z  2025-03-14T03:51:43.8476809Z SETTING_EXPERIMENTS = "experiments" 2025-03-14T03:51:43.8477397Z  2025-03-14T03:51:43.8477830Z LF_FLEET_EXPERIMENT = "lf" 2025-03-14T03:51:43.8478400Z CANARY_FLEET_SUFFIX = ".c" 2025-03-14T03:51:43.8478932Z  2025-03-14T03:51:43.8479331Z  2025-03-14T03:51:43.8479770Z class Experiment(NamedTuple): 2025-03-14T03:51:43.8480361Z  rollout_perc: float = ( 2025-03-14T03:51:43.8481160Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-03-14T03:51:43.8481947Z  ) 2025-03-14T03:51:43.8482405Z  all_branches: bool = ( 2025-03-14T03:51:43.8483182Z  False # If True, the experiment is also enabled on the exception branches 2025-03-14T03:51:43.8484046Z  ) 2025-03-14T03:51:43.8484496Z  default: bool = ( 2025-03-14T03:51:43.8485224Z  True # If True, the experiment is enabled by default for all queries 2025-03-14T03:51:43.8485982Z  ) 2025-03-14T03:51:43.8486405Z  2025-03-14T03:51:43.8486942Z  # Add more fields as needed 2025-03-14T03:51:43.8487509Z  2025-03-14T03:51:43.8487903Z  2025-03-14T03:51:43.8488342Z class Settings(NamedTuple): 2025-03-14T03:51:43.8488976Z  """ 2025-03-14T03:51:43.8489794Z  Settings for the experiments that can be opted into. 2025-03-14T03:51:43.8490479Z  """ 2025-03-14T03:51:43.8490901Z  2025-03-14T03:51:43.8491376Z  experiments: dict[str, Experiment] = {} 2025-03-14T03:51:43.8491979Z  2025-03-14T03:51:43.8492527Z  2025-03-14T03:51:43.8493018Z class ColorFormatter(logging.Formatter): 2025-03-14T03:51:43.8493772Z  """Color codes the log messages based on the log level""" 2025-03-14T03:51:43.8494460Z  2025-03-14T03:51:43.8494858Z  COLORS = { 2025-03-14T03:51:43.8495374Z  "WARNING": "\033[33m", # Yellow 2025-03-14T03:51:43.8495987Z  "ERROR": "\033[31m", # Red 2025-03-14T03:51:43.8496748Z  "CRITICAL": "\033[31m", # Red 2025-03-14T03:51:43.8497365Z  "INFO": "\033[0m", # Reset 2025-03-14T03:51:43.8497963Z  "DEBUG": "\033[0m", # Reset 2025-03-14T03:51:43.8498537Z  } 2025-03-14T03:51:43.8498958Z  2025-03-14T03:51:43.8499454Z  def format(self, record: LogRecord) -> str: 2025-03-14T03:51:43.8500360Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-03-14T03:51:43.8501281Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-03-14T03:51:43.8501964Z  return super().format(record) 2025-03-14T03:51:43.8502547Z  2025-03-14T03:51:43.8502944Z  2025-03-14T03:51:43.8503392Z handler = logging.StreamHandler() 2025-03-14T03:51:43.8504271Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-03-14T03:51:43.8505118Z  2025-03-14T03:51:43.8505653Z log = logging.getLogger(os.path.basename(__file__)) 2025-03-14T03:51:43.8506360Z log.addHandler(handler) 2025-03-14T03:51:43.8507017Z log.setLevel(logging.INFO) 2025-03-14T03:51:43.8507566Z  2025-03-14T03:51:43.8507964Z  2025-03-14T03:51:43.8508503Z def set_github_output(key: str, value: str) -> None: 2025-03-14T03:51:43.8509184Z  """ 2025-03-14T03:51:43.8509809Z  Defines outputs of the github action that invokes this script 2025-03-14T03:51:43.8510566Z  """ 2025-03-14T03:51:43.8511025Z  if not GITHUB_OUTPUT: 2025-03-14T03:51:43.8512279Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-03-14T03:51:43.8513725Z  log.warning( 2025-03-14T03:51:43.8514752Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-03-14T03:51:43.8515826Z  ) 2025-03-14T03:51:43.8516351Z  print(f"::set-output name={key}::{value}") 2025-03-14T03:51:43.8517099Z  return 2025-03-14T03:51:43.8517564Z  2025-03-14T03:51:43.8518110Z  with open(GITHUB_OUTPUT, "a") as f: 2025-03-14T03:51:43.8518797Z  log.info(f"Setting output: {key}='{value}'") 2025-03-14T03:51:43.8519470Z  f.write(f"{key}={value}\n") 2025-03-14T03:51:43.8520043Z  2025-03-14T03:51:43.8520437Z  2025-03-14T03:51:43.8521031Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-03-14T03:51:43.8521796Z  return frozenset( 2025-03-14T03:51:43.8522551Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-03-14T03:51:43.8523340Z  ) 2025-03-14T03:51:43.8523759Z  2025-03-14T03:51:43.8524153Z  2025-03-14T03:51:43.8524577Z def parse_args() -> Any: 2025-03-14T03:51:43.8525273Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-03-14T03:51:43.8526294Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-03-14T03:51:43.8527293Z  parser.add_argument( 2025-03-14T03:51:43.8527862Z  "--github-issue-repo", 2025-03-14T03:51:43.8528464Z  type=str, 2025-03-14T03:51:43.8528985Z  required=False, 2025-03-14T03:51:43.8529693Z  default="pytorch/test-infra", 2025-03-14T03:51:43.8530366Z  help="GitHub repo to get the issue", 2025-03-14T03:51:43.8530979Z  ) 2025-03-14T03:51:43.8531440Z  parser.add_argument( 2025-03-14T03:51:43.8532007Z  "--github-repo", 2025-03-14T03:51:43.8532539Z  type=str, 2025-03-14T03:51:43.8533046Z  required=True, 2025-03-14T03:51:43.8533628Z  help="GitHub repo where CI is running", 2025-03-14T03:51:43.8534251Z  ) 2025-03-14T03:51:43.8534695Z  parser.add_argument( 2025-03-14T03:51:43.8535449Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-03-14T03:51:43.8536213Z  ) 2025-03-14T03:51:43.8536764Z  parser.add_argument( 2025-03-14T03:51:43.8537525Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-03-14T03:51:43.8538321Z  ) 2025-03-14T03:51:43.8538770Z  parser.add_argument( 2025-03-14T03:51:43.8539553Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-03-14T03:51:43.8540359Z  ) 2025-03-14T03:51:43.8540807Z  parser.add_argument( 2025-03-14T03:51:43.8541613Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-03-14T03:51:43.8542444Z  ) 2025-03-14T03:51:43.8542896Z  parser.add_argument( 2025-03-14T03:51:43.8543449Z  "--github-ref-type", 2025-03-14T03:51:43.8544015Z  type=str, 2025-03-14T03:51:43.8544513Z  required=True, 2025-03-14T03:51:43.8545126Z  help="Current GitHub ref type, branch or tag", 2025-03-14T03:51:43.8545773Z  ) 2025-03-14T03:51:43.8546222Z  parser.add_argument( 2025-03-14T03:51:43.8546898Z  "--eligible-experiments", 2025-03-14T03:51:43.8547528Z  type=_str_comma_separated_to_set, 2025-03-14T03:51:43.8548145Z  required=False, 2025-03-14T03:51:43.8548679Z  default="", 2025-03-14T03:51:43.8549837Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-03-14T03:51:43.8550901Z  ) 2025-03-14T03:51:43.8551351Z  parser.add_argument( 2025-03-14T03:51:43.8551894Z  "--pr-number", 2025-03-14T03:51:43.8552422Z  type=str, 2025-03-14T03:51:43.8552925Z  required=False, 2025-03-14T03:51:43.8553452Z  default="", 2025-03-14T03:51:43.8554058Z  help="the optional PR number where this is run", 2025-03-14T03:51:43.8554701Z  ) 2025-03-14T03:51:43.8555117Z  2025-03-14T03:51:43.8555554Z  return parser.parse_args() 2025-03-14T03:51:43.8556124Z  2025-03-14T03:51:43.8556631Z  2025-03-14T03:51:43.8557351Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-03-14T03:51:43.8558266Z  auth = Auth.Token(github_token) 2025-03-14T03:51:43.8558928Z  return Github(auth=auth) 2025-03-14T03:51:43.8559482Z  2025-03-14T03:51:43.8559880Z  2025-03-14T03:51:43.8560644Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-03-14T03:51:43.8561587Z  repo = gh.get_repo(repo) 2025-03-14T03:51:43.8562221Z  return repo.get_issue(number=issue_num) 2025-03-14T03:51:43.8562841Z  2025-03-14T03:51:43.8563237Z  2025-03-14T03:51:43.8563657Z def get_potential_pr_author( 2025-03-14T03:51:43.8564456Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-03-14T03:51:43.8565264Z ) -> str: 2025-03-14T03:51:43.8566162Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-03-14T03:51:43.8567254Z  # Fetch the actual username from the original PR. The PR number is 2025-03-14T03:51:43.8568159Z  # embedded in the tag name: ciflow// 2025-03-14T03:51:43.8568838Z  2025-03-14T03:51:43.8569287Z  gh = get_gh_client(github_token) 2025-03-14T03:51:43.8569869Z  2025-03-14T03:51:43.8570408Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-03-14T03:51:43.8571154Z  split_tag = ref_name.split("/") 2025-03-14T03:51:43.8571754Z  if ( 2025-03-14T03:51:43.8572241Z  len(split_tag) == 3 2025-03-14T03:51:43.8572851Z  and split_tag[0] == "ciflow" 2025-03-14T03:51:43.8573484Z  and split_tag[2].isnumeric() 2025-03-14T03:51:43.8574069Z  ): 2025-03-14T03:51:43.8574564Z  pr_number = split_tag[2] 2025-03-14T03:51:43.8575153Z  try: 2025-03-14T03:51:43.8575695Z  repository = gh.get_repo(repo) 2025-03-14T03:51:43.8576430Z  pull = repository.get_pull(number=int(pr_number)) 2025-03-14T03:51:43.8577243Z  except Exception as e: 2025-03-14T03:51:43.8577873Z  raise Exception( # noqa: TRY002 2025-03-14T03:51:43.8578670Z  f"issue with pull request {pr_number} from repo {repository}" 2025-03-14T03:51:43.8579431Z  ) from e 2025-03-14T03:51:43.8580102Z  return pull.user.login # type: ignore[no-any-return] 2025-03-14T03:51:43.8580933Z  # In all other cases, return the original input username 2025-03-14T03:51:43.8581620Z  return username 2025-03-14T03:51:43.8582111Z  2025-03-14T03:51:43.8582496Z  2025-03-14T03:51:43.8582985Z def is_exception_branch(branch: str) -> bool: 2025-03-14T03:51:43.8583608Z  """ 2025-03-14T03:51:43.8584375Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-03-14T03:51:43.8585406Z  """ 2025-03-14T03:51:43.8586051Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-03-14T03:51:43.8586924Z  2025-03-14T03:51:43.8587312Z  2025-03-14T03:51:43.8587753Z def load_yaml(yaml_text: str) -> Any: 2025-03-14T03:51:43.8588351Z  try: 2025-03-14T03:51:43.8588826Z  data = yaml.safe_load(yaml_text) 2025-03-14T03:51:43.8589429Z  return data 2025-03-14T03:51:43.8589950Z  except yaml.YAMLError: 2025-03-14T03:51:43.8590550Z  log.exception("Error loading YAML") 2025-03-14T03:51:43.8591146Z  raise 2025-03-14T03:51:43.8591599Z  2025-03-14T03:51:43.8591990Z  2025-03-14T03:51:43.8592701Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-03-14T03:51:43.8593568Z  """ 2025-03-14T03:51:43.8594312Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-03-14T03:51:43.8595203Z  2025-03-14T03:51:43.8595823Z  If the issue body contains "---" then the text above that is the settings 2025-03-14T03:51:43.8596818Z  and the text below is the list of opted in users. 2025-03-14T03:51:43.8597463Z  2025-03-14T03:51:43.8598123Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-03-14T03:51:43.8598936Z  """ 2025-03-14T03:51:43.8599476Z  rollout_state_parts = rollout_state.split("---") 2025-03-14T03:51:43.8600180Z  if len(rollout_state_parts) >= 2: 2025-03-14T03:51:43.8601034Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-03-14T03:51:43.8601740Z  else: 2025-03-14T03:51:43.8602198Z  return "", rollout_state 2025-03-14T03:51:43.8602756Z  2025-03-14T03:51:43.8603152Z  2025-03-14T03:51:43.8603615Z class UserOptins(dict[str, list[str]]): 2025-03-14T03:51:43.8604218Z  """ 2025-03-14T03:51:43.8604837Z  Dictionary of users with a list of features they have opted into 2025-03-14T03:51:43.8605592Z  """ 2025-03-14T03:51:43.8606009Z  2025-03-14T03:51:43.8606390Z  2025-03-14T03:51:43.8607102Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-03-14T03:51:43.8607874Z  """ 2025-03-14T03:51:43.8608734Z  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:51:43.8609707Z  2025-03-14T03:51:43.8610639Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-03-14T03:51:43.8611794Z  - Example line: "@User1,lf,split_build" 2025-03-14T03:51:43.8612599Z  - A "#" prefix indicates the user is opted out of all experiments 2025-03-14T03:51:43.8613338Z  2025-03-14T03:51:43.8613720Z  2025-03-14T03:51:43.8614109Z  """ 2025-03-14T03:51:43.8614556Z  optins = UserOptins() 2025-03-14T03:51:43.8615165Z  for user in user_optin_text.split("\n"): 2025-03-14T03:51:43.8615821Z  user = user.strip("\r\n\t -") 2025-03-14T03:51:43.8616563Z  if not user or not user.startswith("@"): 2025-03-14T03:51:43.8617225Z  # Not a valid user. Skip 2025-03-14T03:51:43.8617811Z  continue 2025-03-14T03:51:43.8618289Z  2025-03-14T03:51:43.8618685Z  if user: 2025-03-14T03:51:43.8619230Z  usr_name = user.split(",")[0].strip("@") 2025-03-14T03:51:43.8620040Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-03-14T03:51:43.8620789Z  2025-03-14T03:51:43.8621358Z  return optins 2025-03-14T03:51:43.8621847Z  2025-03-14T03:51:43.8622226Z  2025-03-14T03:51:43.8622799Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-03-14T03:51:43.8623526Z  """ 2025-03-14T03:51:43.8624017Z  Check if the experiment name is valid. 2025-03-14T03:51:43.8624637Z  A valid name: 2025-03-14T03:51:43.8625429Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-03-14T03:51:43.8626620Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-03-14T03:51:43.8627460Z  - Cannot contain spaces 2025-03-14T03:51:43.8628021Z  """ 2025-03-14T03:51:43.8628451Z  2025-03-14T03:51:43.8628984Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-03-14T03:51:43.8629817Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-03-14T03:51:43.8630527Z  2025-03-14T03:51:43.8630930Z  if valid: 2025-03-14T03:51:43.8631410Z  return True 2025-03-14T03:51:43.8631898Z  2025-03-14T03:51:43.8632294Z  log.error( 2025-03-14T03:51:43.8633993Z  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:51:43.8635777Z  ) 2025-03-14T03:51:43.8636201Z  return False 2025-03-14T03:51:43.8636773Z  2025-03-14T03:51:43.8637154Z  2025-03-14T03:51:43.8637881Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-03-14T03:51:43.8638635Z  """ 2025-03-14T03:51:43.8639337Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-03-14T03:51:43.8640172Z  """ 2025-03-14T03:51:43.8640589Z  try: 2025-03-14T03:51:43.8641025Z  if settings_text: 2025-03-14T03:51:43.8641905Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-03-14T03:51:43.8642823Z  # for easy reading 2025-03-14T03:51:43.8643782Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-03-14T03:51:43.8644836Z  # the backtick character in shell commands. 2025-03-14T03:51:43.8645556Z  backtick = chr(96) # backtick character 2025-03-14T03:51:43.8646344Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-03-14T03:51:43.8647226Z  settings = load_yaml(settings_text) 2025-03-14T03:51:43.8647834Z  2025-03-14T03:51:43.8648524Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-03-14T03:51:43.8649395Z  experiments = {} 2025-03-14T03:51:43.8649936Z  2025-03-14T03:51:43.8650584Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-03-14T03:51:43.8651469Z  if not is_valid_experiment_name(exp_name): 2025-03-14T03:51:43.8652744Z  # 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:51:43.8653948Z  continue 2025-03-14T03:51:43.8654480Z  2025-03-14T03:51:43.8654908Z  valid_settings = {} 2025-03-14T03:51:43.8655540Z  for setting in exp_settings: 2025-03-14T03:51:43.8656204Z  if setting not in Experiment._fields: 2025-03-14T03:51:43.8656959Z  log.warning( 2025-03-14T03:51:43.8657930Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-03-14T03:51:43.8658780Z  ) 2025-03-14T03:51:43.8659318Z  else: 2025-03-14T03:51:43.8659952Z  valid_settings[setting] = exp_settings[setting] 2025-03-14T03:51:43.8660621Z  2025-03-14T03:51:43.8661175Z  experiments[exp_name] = Experiment(**valid_settings) 2025-03-14T03:51:43.8662184Z  return Settings(experiments) 2025-03-14T03:51:43.8662834Z  2025-03-14T03:51:43.8663281Z  except Exception: 2025-03-14T03:51:43.8663907Z  log.exception("Failed to parse settings") 2025-03-14T03:51:43.8664547Z  2025-03-14T03:51:43.8664958Z  return Settings() 2025-03-14T03:51:43.8665464Z  2025-03-14T03:51:43.8665848Z  2025-03-14T03:51:43.8666387Z def parse_settings(rollout_state: str) -> Settings: 2025-03-14T03:51:43.8667180Z  """ 2025-03-14T03:51:43.8667719Z  Parse settings, if any, from the rollout state. 2025-03-14T03:51:43.8668365Z  2025-03-14T03:51:43.8668984Z  If the issue body contains "---" then the text above that is the settings 2025-03-14T03:51:43.8669898Z  and the text below is the list of opted in users. 2025-03-14T03:51:43.8670539Z  2025-03-14T03:51:43.8671230Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-03-14T03:51:43.8672079Z  """ 2025-03-14T03:51:43.8672945Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:51:43.8674005Z  return parse_settings_from_text(settings_text) 2025-03-14T03:51:43.8674664Z  2025-03-14T03:51:43.8675196Z  2025-03-14T03:51:43.8675722Z def parse_users(rollout_state: str) -> UserOptins: 2025-03-14T03:51:43.8676565Z  """ 2025-03-14T03:51:43.8677050Z  Parse users from the rollout state. 2025-03-14T03:51:43.8677648Z  2025-03-14T03:51:43.8678039Z  """ 2025-03-14T03:51:43.8678686Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:51:43.8679656Z  return parse_user_opt_in_from_text(users_text) 2025-03-14T03:51:43.8680413Z  2025-03-14T03:51:43.8680952Z  2025-03-14T03:51:43.8681669Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:51:43.8682537Z  """ 2025-03-14T03:51:43.8683055Z  Check if a user is opted into an experiment 2025-03-14T03:51:43.8683696Z  """ 2025-03-14T03:51:43.8684248Z  return experiment_name in user_optins.get(user, []) 2025-03-14T03:51:43.8684910Z  2025-03-14T03:51:43.8685299Z  2025-03-14T03:51:43.8686007Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:51:43.8687007Z  """ 2025-03-14T03:51:43.8687563Z  Check if a user explicitly opted out of an experiment 2025-03-14T03:51:43.8688235Z  """ 2025-03-14T03:51:43.8688840Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-03-14T03:51:43.8689653Z  experiment_optout = "-" + experiment_name 2025-03-14T03:51:43.8690407Z  if experiment_optout not in user_optins.get(user, []): 2025-03-14T03:51:43.8691103Z  return False 2025-03-14T03:51:43.8691584Z  2025-03-14T03:51:43.8692117Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-03-14T03:51:43.8692811Z  log.warning( 2025-03-14T03:51:43.8693748Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-03-14T03:51:43.8694912Z  ) 2025-03-14T03:51:43.8695345Z  2025-03-14T03:51:43.8695745Z  return True 2025-03-14T03:51:43.8696219Z  2025-03-14T03:51:43.8696696Z  2025-03-14T03:51:43.8697117Z def get_runner_prefix( 2025-03-14T03:51:43.8697655Z  rollout_state: str, 2025-03-14T03:51:43.8698233Z  workflow_requestors: Iterable[str], 2025-03-14T03:51:43.8698836Z  branch: str, 2025-03-14T03:51:43.8699442Z  eligible_experiments: frozenset[str] = frozenset(), 2025-03-14T03:51:43.8700142Z  is_canary: bool = False, 2025-03-14T03:51:43.8700685Z ) -> str: 2025-03-14T03:51:43.8701207Z  settings = parse_settings(rollout_state) 2025-03-14T03:51:43.8701894Z  user_optins = parse_users(rollout_state) 2025-03-14T03:51:43.8702492Z  2025-03-14T03:51:43.8702898Z  fleet_prefix = "" 2025-03-14T03:51:43.8703422Z  prefixes = [] 2025-03-14T03:51:43.8704184Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-03-14T03:51:43.8705273Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-03-14T03:51:43.8706095Z  log.info( 2025-03-14T03:51:43.8707045Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-03-14T03:51:43.8707912Z  ) 2025-03-14T03:51:43.8708384Z  continue 2025-03-14T03:51:43.8708873Z  2025-03-14T03:51:43.8709296Z  if eligible_experiments: 2025-03-14T03:51:43.8710097Z  if experiment_name not in eligible_experiments: 2025-03-14T03:51:43.8710845Z  exp_list = ", ".join(eligible_experiments) 2025-03-14T03:51:43.8711486Z  log.info( 2025-03-14T03:51:43.8712470Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-03-14T03:51:43.8713441Z  ) 2025-03-14T03:51:43.8713934Z  continue 2025-03-14T03:51:43.8714527Z  elif not experiment_settings.default: 2025-03-14T03:51:43.8715145Z  log.info( 2025-03-14T03:51:43.8715947Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-03-14T03:51:43.8716937Z  ) 2025-03-14T03:51:43.8717405Z  continue 2025-03-14T03:51:43.8717894Z  2025-03-14T03:51:43.8718430Z  # Is any workflow_requestor opted out to this experiment? 2025-03-14T03:51:43.8719157Z  opted_out_users = [ 2025-03-14T03:51:43.8719711Z  requestor 2025-03-14T03:51:43.8720281Z  for requestor in workflow_requestors 2025-03-14T03:51:43.8721077Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-03-14T03:51:43.8721807Z  ] 2025-03-14T03:51:43.8722230Z  2025-03-14T03:51:43.8722643Z  if opted_out_users: 2025-03-14T03:51:43.8723191Z  log.info( 2025-03-14T03:51:43.8723947Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-03-14T03:51:43.8724745Z  ) 2025-03-14T03:51:43.8725200Z  continue 2025-03-14T03:51:43.8725685Z  2025-03-14T03:51:43.8726210Z  # Is any workflow_requestor opted in to this experiment? 2025-03-14T03:51:43.8727048Z  opted_in_users = [ 2025-03-14T03:51:43.8727608Z  requestor 2025-03-14T03:51:43.8728174Z  for requestor in workflow_requestors 2025-03-14T03:51:43.8728951Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-03-14T03:51:43.8729824Z  ] 2025-03-14T03:51:43.8730263Z  2025-03-14T03:51:43.8730675Z  enabled = False 2025-03-14T03:51:43.8731206Z  if opted_in_users: 2025-03-14T03:51:43.8731753Z  log.info( 2025-03-14T03:51:43.8732481Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-03-14T03:51:43.8733277Z  ) 2025-03-14T03:51:43.8733745Z  enabled = True 2025-03-14T03:51:43.8734279Z  2025-03-14T03:51:43.8734735Z  elif experiment_settings.rollout_perc: 2025-03-14T03:51:43.8735708Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-03-14T03:51:43.8736908Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-03-14T03:51:43.8737668Z  log.info( 2025-03-14T03:51:43.8738702Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-03-14T03:51:43.8739761Z  ) 2025-03-14T03:51:43.8740256Z  enabled = True 2025-03-14T03:51:43.8740804Z  2025-03-14T03:51:43.8741203Z  if enabled: 2025-03-14T03:51:43.8741732Z  label = experiment_name 2025-03-14T03:51:43.8742392Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-03-14T03:51:43.8743348Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-03-14T03:51:43.8744523Z  # - If it's enabled, then we always list it's prefix first 2025-03-14T03:51:43.8745461Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-03-14T03:51:43.8746222Z  if is_canary: 2025-03-14T03:51:43.8746940Z  label += CANARY_FLEET_SUFFIX 2025-03-14T03:51:43.8747579Z  fleet_prefix = label 2025-03-14T03:51:43.8748159Z  else: 2025-03-14T03:51:43.8748681Z  prefixes.append(label) 2025-03-14T03:51:43.8749270Z  2025-03-14T03:51:43.8749683Z  if len(prefixes) > 1: 2025-03-14T03:51:43.8750228Z  log.error( 2025-03-14T03:51:43.8751442Z  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:51:43.8752711Z  ) 2025-03-14T03:51:43.8753178Z  prefixes = prefixes[:1] 2025-03-14T03:51:43.8753742Z  2025-03-14T03:51:43.8754162Z  # Fleet always comes first 2025-03-14T03:51:43.8754738Z  if fleet_prefix: 2025-03-14T03:51:43.8755288Z  prefixes.insert(0, fleet_prefix) 2025-03-14T03:51:43.8755882Z  2025-03-14T03:51:43.8756404Z  return ".".join(prefixes) + "." if prefixes else "" 2025-03-14T03:51:43.8757153Z  2025-03-14T03:51:43.8757537Z  2025-03-14T03:51:43.8758275Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-03-14T03:51:43.8759189Z  """ 2025-03-14T03:51:43.8759882Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-03-14T03:51:43.8760699Z  2025-03-14T03:51:43.8761363Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-03-14T03:51:43.8762173Z  """ 2025-03-14T03:51:43.8762661Z  gh = get_gh_client(github_token) 2025-03-14T03:51:43.8763309Z  issue = get_issue(gh, repo, issue_num) 2025-03-14T03:51:43.8764069Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-03-14T03:51:43.8764897Z  2025-03-14T03:51:43.8765280Z  2025-03-14T03:51:43.8765963Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-03-14T03:51:43.8766937Z  for _ in range(num_retries): 2025-03-14T03:51:43.8767500Z  try: 2025-03-14T03:51:43.8768020Z  req = Request(url=url, headers=headers) 2025-03-14T03:51:43.8768787Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-03-14T03:51:43.8769545Z  return json.loads(content) 2025-03-14T03:51:43.8770158Z  except Exception as e: 2025-03-14T03:51:43.8770811Z  log.warning(f"Could not download {url}: {e}") 2025-03-14T03:51:43.8771455Z  2025-03-14T03:51:43.8772113Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-03-14T03:51:43.8772934Z  return {} 2025-03-14T03:51:43.8842575Z  2025-03-14T03:51:43.8843386Z  2025-03-14T03:51:43.8844200Z @cache 2025-03-14T03:51:43.8845523Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-03-14T03:51:43.8846871Z  """ 2025-03-14T03:51:43.8847374Z  Dynamically get PR information 2025-03-14T03:51:43.8847955Z  """ 2025-03-14T03:51:43.8848556Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-03-14T03:51:43.8849287Z  headers = { 2025-03-14T03:51:43.8849862Z  "Accept": "application/vnd.github.v3+json", 2025-03-14T03:51:43.8850572Z  "Authorization": f"token {github_token}", 2025-03-14T03:51:43.8851186Z  } 2025-03-14T03:51:43.8851943Z  json_response: dict[str, Any] = download_json( 2025-03-14T03:51:43.8852662Z  url=f"{github_api}/issues/{pr_number}", 2025-03-14T03:51:43.8853302Z  headers=headers, 2025-03-14T03:51:43.8853836Z  ) 2025-03-14T03:51:43.8854255Z  2025-03-14T03:51:43.8854666Z  if not json_response: 2025-03-14T03:51:43.8855356Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-03-14T03:51:43.8856069Z  return {} 2025-03-14T03:51:43.8856733Z  2025-03-14T03:51:43.8857161Z  return json_response 2025-03-14T03:51:43.8857688Z  2025-03-14T03:51:43.8858061Z  2025-03-14T03:51:43.8858746Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-03-14T03:51:43.8859574Z  """ 2025-03-14T03:51:43.8860198Z  Dynamically get the latest list of labels from the pull request 2025-03-14T03:51:43.8860940Z  """ 2025-03-14T03:51:43.8861505Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-03-14T03:51:43.8862190Z  return { 2025-03-14T03:51:43.8862877Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-03-14T03:51:43.8863658Z  } 2025-03-14T03:51:43.8864064Z  2025-03-14T03:51:43.8864447Z  2025-03-14T03:51:43.8864849Z def main() -> None: 2025-03-14T03:51:43.8865362Z  args = parse_args() 2025-03-14T03:51:43.8865875Z  2025-03-14T03:51:43.8866348Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-03-14T03:51:43.8867138Z  2025-03-14T03:51:43.8867555Z  # Check if the PR is opt-out 2025-03-14T03:51:43.8868138Z  if args.pr_number: 2025-03-14T03:51:43.8868928Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-03-14T03:51:43.8869789Z  if OPT_OUT_LABEL in labels: 2025-03-14T03:51:43.8870365Z  log.info( 2025-03-14T03:51:43.8871171Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-03-14T03:51:43.8872177Z  ) 2025-03-14T03:51:43.8872836Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:51:43.8873588Z  sys.exit() 2025-03-14T03:51:43.8874087Z  2025-03-14T03:51:43.8874479Z  try: 2025-03-14T03:51:43.8874981Z  rollout_state = get_rollout_state_from_issue( 2025-03-14T03:51:43.8875795Z  args.github_token, args.github_issue_repo, args.github_issue 2025-03-14T03:51:43.8877086Z  ) 2025-03-14T03:51:43.8877541Z  2025-03-14T03:51:43.8878006Z  username = get_potential_pr_author( 2025-03-14T03:51:43.8878635Z  args.github_token, 2025-03-14T03:51:43.8879229Z  args.github_repo, 2025-03-14T03:51:43.8879801Z  args.github_actor, 2025-03-14T03:51:43.8880383Z  args.github_ref_type, 2025-03-14T03:51:43.8880997Z  args.github_branch, 2025-03-14T03:51:43.8881552Z  ) 2025-03-14T03:51:43.8881977Z  2025-03-14T03:51:43.8882521Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-03-14T03:51:43.8883209Z  2025-03-14T03:51:43.8883680Z  runner_label_prefix = get_runner_prefix( 2025-03-14T03:51:43.8884309Z  rollout_state, 2025-03-14T03:51:43.8884893Z  (args.github_issue_owner, username), 2025-03-14T03:51:43.8885515Z  args.github_branch, 2025-03-14T03:51:43.8886113Z  args.eligible_experiments, 2025-03-14T03:51:43.8886904Z  is_canary, 2025-03-14T03:51:43.8887553Z  ) 2025-03-14T03:51:43.8887978Z  2025-03-14T03:51:43.8888393Z  except Exception as e: 2025-03-14T03:51:43.8888926Z  log.error( 2025-03-14T03:51:43.8889733Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-03-14T03:51:43.8890582Z  ) 2025-03-14T03:51:43.8891004Z  2025-03-14T03:51:43.8891599Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:51:43.8892345Z  2025-03-14T03:51:43.8892723Z  2025-03-14T03:51:43.8893121Z if __name__ == "__main__": 2025-03-14T03:51:43.8893646Z  main() 2025-03-14T03:51:43.8894074Z  2025-03-14T03:51:43.8894454Z EOF 2025-03-14T03:51:43.8894842Z  2025-03-14T03:51:43.8895259Z cat runner_determinator.py 2025-03-14T03:51:43.9363827Z shell: /usr/bin/bash -e {0} 2025-03-14T03:51:43.9364739Z env: 2025-03-14T03:51:43.9365548Z GITHUB_TOKEN: *** 2025-03-14T03:51:43.9366030Z ISSUE_NUMBER: 5132 2025-03-14T03:51:43.9366687Z TRIGGERING_ACTOR: pytorchmergebot 2025-03-14T03:51:43.9367249Z ISSUE_OWNER: 2025-03-14T03:51:43.9367699Z CHECK_EXPERIMENTS: 2025-03-14T03:51:43.9368151Z PR_NUMBER: 2025-03-14T03:51:43.9368567Z ##[endgroup] 2025-03-14T03:51:43.9587001Z # flake8: noqa: G004 2025-03-14T03:51:43.9587373Z 2025-03-14T03:51:43.9587841Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-03-14T03:51:43.9588859Z # must be kept in sync. You can do it easily by running the following command: 2025-03-14T03:51:43.9589699Z # python .github/scripts/update_runner_determinator.py 2025-03-14T03:51:43.9590163Z 2025-03-14T03:51:43.9590334Z """ 2025-03-14T03:51:43.9590931Z This runner determinator is used to determine which set of runners to run a 2025-03-14T03:51:43.9591875Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-03-14T03:51:43.9592857Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-03-14T03:51:43.9593720Z of which runners should be used to run which job. 2025-03-14T03:51:43.9594148Z 2025-03-14T03:51:43.9594799Z The configuration has two parts, the settings and a list of opted-in users, 2025-03-14T03:51:43.9595745Z separated by a line containing "---". If the line is not present, the 2025-03-14T03:51:43.9597068Z settings are considered to be empty with only the second part, the user 2025-03-14T03:51:43.9597811Z list, defined. 2025-03-14T03:51:43.9598063Z 2025-03-14T03:51:43.9598442Z The first part is a YAML block that defines the rollout settings. This can be 2025-03-14T03:51:43.9599415Z used to define any settings that are needed to determine which runners to use. 2025-03-14T03:51:43.9600282Z It's fields are defined by the RolloutSettings class below. 2025-03-14T03:51:43.9600752Z 2025-03-14T03:51:43.9601143Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-03-14T03:51:43.9602070Z The user list is also a comma separated list of additional features or 2025-03-14T03:51:43.9602839Z experiments which the user could be opted in to. 2025-03-14T03:51:43.9603266Z 2025-03-14T03:51:43.9603476Z The user list has the following rules: 2025-03-14T03:51:43.9603837Z 2025-03-14T03:51:43.9604158Z - Users are GitHub usernames, which must start with the @ prefix 2025-03-14T03:51:43.9605054Z - Each user is also a comma-separated list of features/experiments to enable 2025-03-14T03:51:43.9605858Z - A "#" prefix opts the user out of all experiments 2025-03-14T03:51:43.9606271Z 2025-03-14T03:51:43.9606635Z Example config: 2025-03-14T03:51:43.9607131Z # A list of experiments that can be opted into. 2025-03-14T03:51:43.9607842Z # This defines the behavior they'll induce when opted into. 2025-03-14T03:51:43.9608493Z # Expected syntax is: 2025-03-14T03:51:43.9609165Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-03-14T03:51:43.9610353Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-03-14T03:51:43.9611017Z 2025-03-14T03:51:43.9611195Z experiments: 2025-03-14T03:51:43.9611597Z lf: 2025-03-14T03:51:43.9611993Z rollout_percent: 25 2025-03-14T03:51:43.9612463Z all_branches: false 2025-03-14T03:51:43.9612923Z default: true 2025-03-14T03:51:43.9613342Z --- 2025-03-14T03:51:43.9613558Z 2025-03-14T03:51:43.9613726Z # Opt-ins: 2025-03-14T03:51:43.9614325Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-03-14T03:51:43.9615237Z # and specifying experiments to enable in a comma-separated list. 2025-03-14T03:51:43.9616042Z # To always opt out of an experiment, prefix it with a "-". 2025-03-14T03:51:43.9616993Z # Experiments should be from the above list. 2025-03-14T03:51:43.9617415Z 2025-03-14T03:51:43.9617606Z @User1,-lf,split_build 2025-03-14T03:51:43.9618065Z @User2,lf 2025-03-14T03:51:43.9618478Z @User3,split_build 2025-03-14T03:51:43.9618912Z """ 2025-03-14T03:51:43.9619114Z 2025-03-14T03:51:43.9619284Z import json 2025-03-14T03:51:43.9619679Z import logging 2025-03-14T03:51:43.9620115Z import os 2025-03-14T03:51:43.9620498Z import random 2025-03-14T03:51:43.9620886Z import re 2025-03-14T03:51:43.9621255Z import sys 2025-03-14T03:51:43.9621668Z from argparse import ArgumentParser 2025-03-14T03:51:43.9622236Z from collections.abc import Iterable 2025-03-14T03:51:43.9622799Z from functools import cache 2025-03-14T03:51:43.9623299Z from logging import LogRecord 2025-03-14T03:51:43.9623806Z from typing import Any, NamedTuple 2025-03-14T03:51:43.9624368Z from urllib.request import Request, urlopen 2025-03-14T03:51:43.9624773Z 2025-03-14T03:51:43.9624950Z import yaml 2025-03-14T03:51:43.9625380Z from github import Auth, Github 2025-03-14T03:51:43.9625899Z from github.Issue import Issue 2025-03-14T03:51:43.9626219Z 2025-03-14T03:51:43.9626225Z 2025-03-14T03:51:43.9626884Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-03-14T03:51:43.9627658Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-03-14T03:51:43.9628583Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-03-14T03:51:43.9629361Z 2025-03-14T03:51:43.9629602Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-03-14T03:51:43.9630209Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-03-14T03:51:43.9630734Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-03-14T03:51:43.9631314Z OPT_OUT_LABEL = "no-runner-experiments" 2025-03-14T03:51:43.9631694Z 2025-03-14T03:51:43.9631898Z SETTING_EXPERIMENTS = "experiments" 2025-03-14T03:51:43.9632250Z 2025-03-14T03:51:43.9632445Z LF_FLEET_EXPERIMENT = "lf" 2025-03-14T03:51:43.9632928Z CANARY_FLEET_SUFFIX = ".c" 2025-03-14T03:51:43.9633218Z 2025-03-14T03:51:43.9633224Z 2025-03-14T03:51:43.9633424Z class Experiment(NamedTuple): 2025-03-14T03:51:43.9633923Z rollout_perc: float = ( 2025-03-14T03:51:43.9634594Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-03-14T03:51:43.9635313Z ) 2025-03-14T03:51:43.9635698Z all_branches: bool = ( 2025-03-14T03:51:43.9636351Z False # If True, the experiment is also enabled on the exception branches 2025-03-14T03:51:43.9637311Z ) 2025-03-14T03:51:43.9637691Z default: bool = ( 2025-03-14T03:51:43.9638296Z True # If True, the experiment is enabled by default for all queries 2025-03-14T03:51:43.9638985Z ) 2025-03-14T03:51:43.9639192Z 2025-03-14T03:51:43.9639393Z # Add more fields as needed 2025-03-14T03:51:43.9639706Z 2025-03-14T03:51:43.9639713Z 2025-03-14T03:51:43.9639907Z class Settings(NamedTuple): 2025-03-14T03:51:43.9640380Z """ 2025-03-14T03:51:43.9640853Z Settings for the experiments that can be opted into. 2025-03-14T03:51:43.9641460Z """ 2025-03-14T03:51:43.9641668Z 2025-03-14T03:51:43.9641885Z experiments: dict[str, Experiment] = {} 2025-03-14T03:51:43.9642264Z 2025-03-14T03:51:43.9642270Z 2025-03-14T03:51:43.9642633Z class ColorFormatter(logging.Formatter): 2025-03-14T03:51:43.9643302Z """Color codes the log messages based on the log level""" 2025-03-14T03:51:43.9643762Z 2025-03-14T03:51:43.9643931Z COLORS = { 2025-03-14T03:51:43.9644352Z "WARNING": "\033[33m", # Yellow 2025-03-14T03:51:43.9644890Z "ERROR": "\033[31m", # Red 2025-03-14T03:51:43.9645403Z "CRITICAL": "\033[31m", # Red 2025-03-14T03:51:43.9645925Z "INFO": "\033[0m", # Reset 2025-03-14T03:51:43.9646427Z "DEBUG": "\033[0m", # Reset 2025-03-14T03:51:43.9647096Z } 2025-03-14T03:51:43.9647308Z 2025-03-14T03:51:43.9647535Z def format(self, record: LogRecord) -> str: 2025-03-14T03:51:43.9648330Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-03-14T03:51:43.9649150Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-03-14T03:51:43.9649760Z return super().format(record) 2025-03-14T03:51:43.9650127Z 2025-03-14T03:51:43.9650133Z 2025-03-14T03:51:43.9650341Z handler = logging.StreamHandler() 2025-03-14T03:51:43.9651090Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-03-14T03:51:43.9651683Z 2025-03-14T03:51:43.9651945Z log = logging.getLogger(os.path.basename(__file__)) 2025-03-14T03:51:43.9652558Z log.addHandler(handler) 2025-03-14T03:51:43.9653022Z log.setLevel(logging.INFO) 2025-03-14T03:51:43.9653319Z 2025-03-14T03:51:43.9653325Z 2025-03-14T03:51:43.9653589Z def set_github_output(key: str, value: str) -> None: 2025-03-14T03:51:43.9654178Z """ 2025-03-14T03:51:43.9654703Z Defines outputs of the github action that invokes this script 2025-03-14T03:51:43.9655375Z """ 2025-03-14T03:51:43.9655760Z if not GITHUB_OUTPUT: 2025-03-14T03:51:43.9657128Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-03-14T03:51:43.9658334Z log.warning( 2025-03-14T03:51:43.9659260Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-03-14T03:51:43.9660258Z ) 2025-03-14T03:51:43.9670891Z print(f"::set-output name={key}::{value}") 2025-03-14T03:51:43.9671754Z return 2025-03-14T03:51:43.9672006Z 2025-03-14T03:51:43.9672213Z with open(GITHUB_OUTPUT, "a") as f: 2025-03-14T03:51:43.9672818Z log.info(f"Setting output: {key}='{value}'") 2025-03-14T03:51:43.9673413Z f.write(f"{key}={value}\n") 2025-03-14T03:51:43.9673760Z 2025-03-14T03:51:43.9673767Z 2025-03-14T03:51:43.9674082Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-03-14T03:51:43.9674758Z return frozenset( 2025-03-14T03:51:43.9675394Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-03-14T03:51:43.9676108Z ) 2025-03-14T03:51:43.9676317Z 2025-03-14T03:51:43.9676324Z 2025-03-14T03:51:43.9676749Z def parse_args() -> Any: 2025-03-14T03:51:43.9677365Z parser = ArgumentParser("Get dynamic rollout settings") 2025-03-14T03:51:43.9678320Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-03-14T03:51:43.9679150Z parser.add_argument( 2025-03-14T03:51:43.9679638Z "--github-issue-repo", 2025-03-14T03:51:43.9680123Z type=str, 2025-03-14T03:51:43.9680542Z required=False, 2025-03-14T03:51:43.9681021Z default="pytorch/test-infra", 2025-03-14T03:51:43.9681585Z help="GitHub repo to get the issue", 2025-03-14T03:51:43.9682115Z ) 2025-03-14T03:51:43.9682500Z parser.add_argument( 2025-03-14T03:51:43.9682964Z "--github-repo", 2025-03-14T03:51:43.9683409Z type=str, 2025-03-14T03:51:43.9683829Z required=True, 2025-03-14T03:51:43.9684297Z help="GitHub repo where CI is running", 2025-03-14T03:51:43.9684847Z ) 2025-03-14T03:51:43.9685232Z parser.add_argument( 2025-03-14T03:51:43.9685869Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-03-14T03:51:43.9686932Z ) 2025-03-14T03:51:43.9687363Z parser.add_argument( 2025-03-14T03:51:43.9688034Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-03-14T03:51:43.9688758Z ) 2025-03-14T03:51:43.9689143Z parser.add_argument( 2025-03-14T03:51:43.9689803Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-03-14T03:51:43.9690518Z ) 2025-03-14T03:51:43.9690897Z parser.add_argument( 2025-03-14T03:51:43.9691575Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-03-14T03:51:43.9692329Z ) 2025-03-14T03:51:43.9692719Z parser.add_argument( 2025-03-14T03:51:43.9693186Z "--github-ref-type", 2025-03-14T03:51:43.9693664Z type=str, 2025-03-14T03:51:43.9694078Z required=True, 2025-03-14T03:51:43.9694589Z help="Current GitHub ref type, branch or tag", 2025-03-14T03:51:43.9695168Z ) 2025-03-14T03:51:43.9695548Z parser.add_argument( 2025-03-14T03:51:43.9696029Z "--eligible-experiments", 2025-03-14T03:51:43.9696757Z type=_str_comma_separated_to_set, 2025-03-14T03:51:43.9697338Z required=False, 2025-03-14T03:51:43.9697788Z default="", 2025-03-14T03:51:43.9698687Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-03-14T03:51:43.9699701Z ) 2025-03-14T03:51:43.9700086Z parser.add_argument( 2025-03-14T03:51:43.9700548Z "--pr-number", 2025-03-14T03:51:43.9700997Z type=str, 2025-03-14T03:51:43.9701406Z required=False, 2025-03-14T03:51:43.9701850Z default="", 2025-03-14T03:51:43.9702338Z help="the optional PR number where this is run", 2025-03-14T03:51:43.9702921Z ) 2025-03-14T03:51:43.9703124Z 2025-03-14T03:51:43.9703327Z return parser.parse_args() 2025-03-14T03:51:43.9703650Z 2025-03-14T03:51:43.9703657Z 2025-03-14T03:51:43.9704085Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-03-14T03:51:43.9704888Z auth = Auth.Token(github_token) 2025-03-14T03:51:43.9705420Z return Github(auth=auth) 2025-03-14T03:51:43.9705723Z 2025-03-14T03:51:43.9705875Z 2025-03-14T03:51:43.9706378Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-03-14T03:51:43.9707519Z repo = gh.get_repo(repo) 2025-03-14T03:51:43.9708042Z return repo.get_issue(number=issue_num) 2025-03-14T03:51:43.9708429Z 2025-03-14T03:51:43.9708436Z 2025-03-14T03:51:43.9708627Z def get_potential_pr_author( 2025-03-14T03:51:43.9709287Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-03-14T03:51:43.9710007Z ) -> str: 2025-03-14T03:51:43.9710542Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-03-14T03:51:43.9711385Z # Fetch the actual username from the original PR. The PR number is 2025-03-14T03:51:43.9712167Z # embedded in the tag name: ciflow// 2025-03-14T03:51:43.9712608Z 2025-03-14T03:51:43.9712805Z gh = get_gh_client(github_token) 2025-03-14T03:51:43.9713157Z 2025-03-14T03:51:43.9713431Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-03-14T03:51:43.9714089Z split_tag = ref_name.split("/") 2025-03-14T03:51:43.9714621Z if ( 2025-03-14T03:51:43.9715023Z len(split_tag) == 3 2025-03-14T03:51:43.9715513Z and split_tag[0] == "ciflow" 2025-03-14T03:51:43.9716057Z and split_tag[2].isnumeric() 2025-03-14T03:51:43.9716781Z ): 2025-03-14T03:51:43.9717197Z pr_number = split_tag[2] 2025-03-14T03:51:43.9717703Z try: 2025-03-14T03:51:43.9718140Z repository = gh.get_repo(repo) 2025-03-14T03:51:43.9718788Z pull = repository.get_pull(number=int(pr_number)) 2025-03-14T03:51:43.9719416Z except Exception as e: 2025-03-14T03:51:43.9719990Z raise Exception( # noqa: TRY002 2025-03-14T03:51:43.9720850Z f"issue with pull request {pr_number} from repo {repository}" 2025-03-14T03:51:43.9721539Z ) from e 2025-03-14T03:51:43.9722094Z return pull.user.login # type: ignore[no-any-return] 2025-03-14T03:51:43.9722832Z # In all other cases, return the original input username 2025-03-14T03:51:43.9723447Z return username 2025-03-14T03:51:43.9723693Z 2025-03-14T03:51:43.9723700Z 2025-03-14T03:51:43.9723938Z def is_exception_branch(branch: str) -> bool: 2025-03-14T03:51:43.9724495Z """ 2025-03-14T03:51:43.9725181Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-03-14T03:51:43.9725999Z """ 2025-03-14T03:51:43.9726770Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-03-14T03:51:43.9727355Z 2025-03-14T03:51:43.9727362Z 2025-03-14T03:51:43.9727578Z def load_yaml(yaml_text: str) -> Any: 2025-03-14T03:51:43.9728102Z try: 2025-03-14T03:51:43.9728511Z data = yaml.safe_load(yaml_text) 2025-03-14T03:51:43.9729036Z return data 2025-03-14T03:51:43.9729462Z except yaml.YAMLError: 2025-03-14T03:51:43.9729958Z log.exception("Error loading YAML") 2025-03-14T03:51:43.9730495Z raise 2025-03-14T03:51:43.9730718Z 2025-03-14T03:51:43.9730726Z 2025-03-14T03:51:43.9731157Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-03-14T03:51:43.9731935Z """ 2025-03-14T03:51:43.9732581Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-03-14T03:51:43.9733219Z 2025-03-14T03:51:43.9733577Z If the issue body contains "---" then the text above that is the settings 2025-03-14T03:51:43.9734366Z and the text below is the list of opted in users. 2025-03-14T03:51:43.9734788Z 2025-03-14T03:51:43.9735181Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-03-14T03:51:43.9735916Z """ 2025-03-14T03:51:43.9736377Z rollout_state_parts = rollout_state.split("---") 2025-03-14T03:51:43.9737109Z if len(rollout_state_parts) >= 2: 2025-03-14T03:51:43.9737731Z return rollout_state_parts[0], rollout_state_parts[1] 2025-03-14T03:51:43.9738496Z else: 2025-03-14T03:51:43.9738884Z return "", rollout_state 2025-03-14T03:51:43.9739213Z 2025-03-14T03:51:43.9739220Z 2025-03-14T03:51:43.9739427Z class UserOptins(dict[str, list[str]]): 2025-03-14T03:51:43.9739950Z """ 2025-03-14T03:51:43.9740487Z Dictionary of users with a list of features they have opted into 2025-03-14T03:51:43.9741171Z """ 2025-03-14T03:51:43.9741375Z 2025-03-14T03:51:43.9741382Z 2025-03-14T03:51:43.9741734Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-03-14T03:51:43.9742425Z """ 2025-03-14T03:51:43.9743167Z 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:51:43.9743899Z 2025-03-14T03:51:43.9744554Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-03-14T03:51:43.9745610Z - Example line: "@User1,lf,split_build" 2025-03-14T03:51:43.9746327Z - A "#" prefix indicates the user is opted out of all experiments 2025-03-14T03:51:43.9747154Z 2025-03-14T03:51:43.9747162Z 2025-03-14T03:51:43.9747340Z """ 2025-03-14T03:51:43.9747726Z optins = UserOptins() 2025-03-14T03:51:43.9748235Z for user in user_optin_text.split("\n"): 2025-03-14T03:51:43.9748810Z user = user.strip("\r\n\t -") 2025-03-14T03:51:43.9749368Z if not user or not user.startswith("@"): 2025-03-14T03:51:43.9749956Z # Not a valid user. Skip 2025-03-14T03:51:43.9750457Z continue 2025-03-14T03:51:43.9750710Z 2025-03-14T03:51:43.9750874Z if user: 2025-03-14T03:51:43.9751328Z usr_name = user.split(",")[0].strip("@") 2025-03-14T03:51:43.9752062Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-03-14T03:51:43.9752720Z 2025-03-14T03:51:43.9752910Z return optins 2025-03-14T03:51:43.9753158Z 2025-03-14T03:51:43.9753164Z 2025-03-14T03:51:43.9753468Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-03-14T03:51:43.9754103Z """ 2025-03-14T03:51:43.9754517Z Check if the experiment name is valid. 2025-03-14T03:51:43.9755059Z A valid name: 2025-03-14T03:51:43.9755722Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-03-14T03:51:43.9757063Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-03-14T03:51:43.9757850Z - Cannot contain spaces 2025-03-14T03:51:43.9758330Z """ 2025-03-14T03:51:43.9758539Z 2025-03-14T03:51:43.9758812Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-03-14T03:51:43.9759555Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-03-14T03:51:43.9760017Z 2025-03-14T03:51:43.9760191Z if valid: 2025-03-14T03:51:43.9760600Z return True 2025-03-14T03:51:43.9760854Z 2025-03-14T03:51:43.9761024Z log.error( 2025-03-14T03:51:43.9762567Z 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:51:43.9764263Z ) 2025-03-14T03:51:43.9764635Z return False 2025-03-14T03:51:43.9764872Z 2025-03-14T03:51:43.9764879Z 2025-03-14T03:51:43.9765203Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-03-14T03:51:43.9765864Z """ 2025-03-14T03:51:43.9766586Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-03-14T03:51:43.9767373Z """ 2025-03-14T03:51:43.9767751Z try: 2025-03-14T03:51:43.9768143Z if settings_text: 2025-03-14T03:51:43.9768923Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-03-14T03:51:43.9769805Z # for easy reading 2025-03-14T03:51:43.9770640Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-03-14T03:51:43.9771787Z # the backtick character in shell commands. 2025-03-14T03:51:43.9772421Z backtick = chr(96) # backtick character 2025-03-14T03:51:43.9773123Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-03-14T03:51:43.9773824Z settings = load_yaml(settings_text) 2025-03-14T03:51:43.9774220Z 2025-03-14T03:51:43.9774642Z # For now we just load experiments. We can expand this if/when we add more settings 2025-03-14T03:51:43.9775448Z experiments = {} 2025-03-14T03:51:43.9775756Z 2025-03-14T03:51:43.9776130Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-03-14T03:51:43.9777033Z if not is_valid_experiment_name(exp_name): 2025-03-14T03:51:43.9778226Z # 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:51:43.9779349Z continue 2025-03-14T03:51:43.9779667Z 2025-03-14T03:51:43.9779859Z valid_settings = {} 2025-03-14T03:51:43.9780413Z for setting in exp_settings: 2025-03-14T03:51:43.9781017Z if setting not in Experiment._fields: 2025-03-14T03:51:43.9781607Z log.warning( 2025-03-14T03:51:43.9782350Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-03-14T03:51:43.9783110Z ) 2025-03-14T03:51:43.9783568Z else: 2025-03-14T03:51:43.9784099Z valid_settings[setting] = exp_settings[setting] 2025-03-14T03:51:43.9784566Z 2025-03-14T03:51:43.9784868Z experiments[exp_name] = Experiment(**valid_settings) 2025-03-14T03:51:43.9785689Z return Settings(experiments) 2025-03-14T03:51:43.9786073Z 2025-03-14T03:51:43.9786257Z except Exception: 2025-03-14T03:51:43.9786863Z log.exception("Failed to parse settings") 2025-03-14T03:51:43.9787289Z 2025-03-14T03:51:43.9787468Z return Settings() 2025-03-14T03:51:43.9787737Z 2025-03-14T03:51:43.9787743Z 2025-03-14T03:51:43.9788003Z def parse_settings(rollout_state: str) -> Settings: 2025-03-14T03:51:43.9788605Z """ 2025-03-14T03:51:43.9789063Z Parse settings, if any, from the rollout state. 2025-03-14T03:51:43.9789487Z 2025-03-14T03:51:43.9789848Z If the issue body contains "---" then the text above that is the settings 2025-03-14T03:51:43.9790648Z and the text below is the list of opted in users. 2025-03-14T03:51:43.9791082Z 2025-03-14T03:51:43.9791542Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-03-14T03:51:43.9792327Z """ 2025-03-14T03:51:43.9792910Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:51:43.9793708Z return parse_settings_from_text(settings_text) 2025-03-14T03:51:43.9794128Z 2025-03-14T03:51:43.9794135Z 2025-03-14T03:51:43.9794387Z def parse_users(rollout_state: str) -> UserOptins: 2025-03-14T03:51:43.9794987Z """ 2025-03-14T03:51:43.9795394Z Parse users from the rollout state. 2025-03-14T03:51:43.9795761Z 2025-03-14T03:51:43.9795932Z """ 2025-03-14T03:51:43.9796574Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-03-14T03:51:43.9797352Z return parse_user_opt_in_from_text(users_text) 2025-03-14T03:51:43.9797769Z 2025-03-14T03:51:43.9797775Z 2025-03-14T03:51:43.9798204Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:51:43.9798993Z """ 2025-03-14T03:51:43.9799422Z Check if a user is opted into an experiment 2025-03-14T03:51:43.9799983Z """ 2025-03-14T03:51:43.9800451Z return experiment_name in user_optins.get(user, []) 2025-03-14T03:51:43.9800896Z 2025-03-14T03:51:43.9800903Z 2025-03-14T03:51:43.9801342Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-03-14T03:51:43.9802289Z """ 2025-03-14T03:51:43.9802765Z Check if a user explicitly opted out of an experiment 2025-03-14T03:51:43.9803369Z """ 2025-03-14T03:51:43.9803898Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-03-14T03:51:43.9804617Z experiment_optout = "-" + experiment_name 2025-03-14T03:51:43.9805283Z if experiment_optout not in user_optins.get(user, []): 2025-03-14T03:51:43.9805909Z return False 2025-03-14T03:51:43.9806174Z 2025-03-14T03:51:43.9806546Z if is_user_opted_in(user, user_optins, experiment_name): 2025-03-14T03:51:43.9807188Z log.warning( 2025-03-14T03:51:43.9808033Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-03-14T03:51:43.9808973Z ) 2025-03-14T03:51:43.9809201Z 2025-03-14T03:51:43.9809376Z return True 2025-03-14T03:51:43.9809618Z 2025-03-14T03:51:43.9809625Z 2025-03-14T03:51:43.9809811Z def get_runner_prefix( 2025-03-14T03:51:43.9810278Z rollout_state: str, 2025-03-14T03:51:43.9810755Z workflow_requestors: Iterable[str], 2025-03-14T03:51:43.9811283Z branch: str, 2025-03-14T03:51:43.9811790Z eligible_experiments: frozenset[str] = frozenset(), 2025-03-14T03:51:43.9812421Z is_canary: bool = False, 2025-03-14T03:51:43.9812897Z ) -> str: 2025-03-14T03:51:43.9813328Z settings = parse_settings(rollout_state) 2025-03-14T03:51:43.9813926Z user_optins = parse_users(rollout_state) 2025-03-14T03:51:43.9814318Z 2025-03-14T03:51:43.9814498Z fleet_prefix = "" 2025-03-14T03:51:43.9814939Z prefixes = [] 2025-03-14T03:51:43.9815590Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-03-14T03:51:43.9816671Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-03-14T03:51:43.9817590Z log.info( 2025-03-14T03:51:43.9818302Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-03-14T03:51:43.9819116Z ) 2025-03-14T03:51:43.9819509Z continue 2025-03-14T03:51:43.9819765Z 2025-03-14T03:51:43.9819994Z if eligible_experiments: 2025-03-14T03:51:43.9820572Z if experiment_name not in eligible_experiments: 2025-03-14T03:51:43.9821225Z exp_list = ", ".join(eligible_experiments) 2025-03-14T03:51:43.9821809Z log.info( 2025-03-14T03:51:43.9822624Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-03-14T03:51:43.9823514Z ) 2025-03-14T03:51:43.9823922Z continue 2025-03-14T03:51:43.9824401Z elif not experiment_settings.default: 2025-03-14T03:51:43.9824966Z log.info( 2025-03-14T03:51:43.9825654Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-03-14T03:51:43.9826542Z ) 2025-03-14T03:51:43.9826948Z continue 2025-03-14T03:51:43.9827197Z 2025-03-14T03:51:43.9827490Z # Is any workflow_requestor opted out to this experiment? 2025-03-14T03:51:43.9828140Z opted_out_users = [ 2025-03-14T03:51:43.9828604Z requestor 2025-03-14T03:51:43.9829078Z for requestor in workflow_requestors 2025-03-14T03:51:43.9829772Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-03-14T03:51:43.9830432Z ] 2025-03-14T03:51:43.9830644Z 2025-03-14T03:51:43.9830827Z if opted_out_users: 2025-03-14T03:51:43.9831298Z log.info( 2025-03-14T03:51:43.9831942Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-03-14T03:51:43.9832692Z ) 2025-03-14T03:51:43.9833092Z continue 2025-03-14T03:51:43.9833348Z 2025-03-14T03:51:43.9833629Z # Is any workflow_requestor opted in to this experiment? 2025-03-14T03:51:43.9834269Z opted_in_users = [ 2025-03-14T03:51:43.9834730Z requestor 2025-03-14T03:51:43.9835350Z for requestor in workflow_requestors 2025-03-14T03:51:43.9836047Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-03-14T03:51:43.9836811Z ] 2025-03-14T03:51:43.9837031Z 2025-03-14T03:51:43.9837216Z enabled = False 2025-03-14T03:51:43.9837670Z if opted_in_users: 2025-03-14T03:51:43.9838133Z log.info( 2025-03-14T03:51:43.9838767Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-03-14T03:51:43.9839485Z ) 2025-03-14T03:51:43.9839913Z enabled = True 2025-03-14T03:51:43.9840215Z 2025-03-14T03:51:43.9840445Z elif experiment_settings.rollout_perc: 2025-03-14T03:51:43.9841326Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-03-14T03:51:43.9842315Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-03-14T03:51:43.9843011Z log.info( 2025-03-14T03:51:43.9843926Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-03-14T03:51:43.9845080Z ) 2025-03-14T03:51:43.9845524Z enabled = True 2025-03-14T03:51:43.9845836Z 2025-03-14T03:51:43.9846015Z if enabled: 2025-03-14T03:51:43.9846556Z label = experiment_name 2025-03-14T03:51:43.9847131Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-03-14T03:51:43.9848001Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-03-14T03:51:43.9848927Z # - If it's enabled, then we always list it's prefix first 2025-03-14T03:51:43.9849852Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-03-14T03:51:43.9850564Z if is_canary: 2025-03-14T03:51:43.9851074Z label += CANARY_FLEET_SUFFIX 2025-03-14T03:51:43.9851645Z fleet_prefix = label 2025-03-14T03:51:43.9852168Z else: 2025-03-14T03:51:43.9852618Z prefixes.append(label) 2025-03-14T03:51:43.9852992Z 2025-03-14T03:51:43.9853187Z if len(prefixes) > 1: 2025-03-14T03:51:43.9853658Z log.error( 2025-03-14T03:51:43.9854757Z 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:51:43.9855976Z ) 2025-03-14T03:51:43.9856378Z prefixes = prefixes[:1] 2025-03-14T03:51:43.9856805Z 2025-03-14T03:51:43.9857008Z # Fleet always comes first 2025-03-14T03:51:43.9857501Z if fleet_prefix: 2025-03-14T03:51:43.9857952Z prefixes.insert(0, fleet_prefix) 2025-03-14T03:51:43.9858339Z 2025-03-14T03:51:43.9858598Z return ".".join(prefixes) + "." if prefixes else "" 2025-03-14T03:51:43.9859038Z 2025-03-14T03:51:43.9859045Z 2025-03-14T03:51:43.9859508Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-03-14T03:51:43.9860344Z """ 2025-03-14T03:51:43.9860951Z Gets the first comment of the issue, which contains the desired rollout state. 2025-03-14T03:51:43.9861550Z 2025-03-14T03:51:43.9861961Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-03-14T03:51:43.9862715Z """ 2025-03-14T03:51:43.9863118Z gh = get_gh_client(github_token) 2025-03-14T03:51:43.9863683Z issue = get_issue(gh, repo, issue_num) 2025-03-14T03:51:43.9864344Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-03-14T03:51:43.9864816Z 2025-03-14T03:51:43.9864822Z 2025-03-14T03:51:43.9865252Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-03-14T03:51:43.9866058Z for _ in range(num_retries): 2025-03-14T03:51:43.9866646Z try: 2025-03-14T03:51:43.9867098Z req = Request(url=url, headers=headers) 2025-03-14T03:51:43.9867793Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-03-14T03:51:43.9868610Z return json.loads(content) 2025-03-14T03:51:43.9869161Z except Exception as e: 2025-03-14T03:51:43.9869717Z log.warning(f"Could not download {url}: {e}") 2025-03-14T03:51:43.9870148Z 2025-03-14T03:51:43.9870542Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-03-14T03:51:43.9871297Z return {} 2025-03-14T03:51:43.9871532Z 2025-03-14T03:51:43.9871539Z 2025-03-14T03:51:43.9871704Z @cache 2025-03-14T03:51:43.9872351Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-03-14T03:51:43.9873146Z """ 2025-03-14T03:51:43.9873550Z Dynamically get PR information 2025-03-14T03:51:43.9874062Z """ 2025-03-14T03:51:43.9874588Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-03-14T03:51:43.9875251Z headers = { 2025-03-14T03:51:43.9875729Z "Accept": "application/vnd.github.v3+json", 2025-03-14T03:51:43.9876359Z "Authorization": f"token {github_token}", 2025-03-14T03:51:43.9877040Z } 2025-03-14T03:51:43.9877480Z json_response: dict[str, Any] = download_json( 2025-03-14T03:51:43.9878112Z url=f"{github_api}/issues/{pr_number}", 2025-03-14T03:51:43.9878681Z headers=headers, 2025-03-14T03:51:43.9879120Z ) 2025-03-14T03:51:43.9879329Z 2025-03-14T03:51:43.9879519Z if not json_response: 2025-03-14T03:51:43.9880101Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-03-14T03:51:43.9880745Z return {} 2025-03-14T03:51:43.9880985Z 2025-03-14T03:51:43.9881177Z return json_response 2025-03-14T03:51:43.9881454Z 2025-03-14T03:51:43.9881461Z 2025-03-14T03:51:43.9881879Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-03-14T03:51:43.9882784Z """ 2025-03-14T03:51:43.9883353Z Dynamically get the latest list of labels from the pull request 2025-03-14T03:51:43.9884053Z """ 2025-03-14T03:51:43.9884547Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-03-14T03:51:43.9885188Z return { 2025-03-14T03:51:43.9885797Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-03-14T03:51:43.9886646Z } 2025-03-14T03:51:43.9886863Z 2025-03-14T03:51:43.9886869Z 2025-03-14T03:51:43.9887048Z def main() -> None: 2025-03-14T03:51:43.9887483Z args = parse_args() 2025-03-14T03:51:43.9887759Z 2025-03-14T03:51:43.9887988Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-03-14T03:51:43.9888395Z 2025-03-14T03:51:43.9888593Z # Check if the PR is opt-out 2025-03-14T03:51:43.9889103Z if args.pr_number: 2025-03-14T03:51:43.9889773Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-03-14T03:51:43.9890575Z if OPT_OUT_LABEL in labels: 2025-03-14T03:51:43.9891098Z log.info( 2025-03-14T03:51:43.9891819Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-03-14T03:51:43.9892643Z ) 2025-03-14T03:51:43.9893207Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:51:43.9893918Z sys.exit() 2025-03-14T03:51:43.9894178Z 2025-03-14T03:51:43.9894349Z try: 2025-03-14T03:51:43.9894795Z rollout_state = get_rollout_state_from_issue( 2025-03-14T03:51:43.9895526Z args.github_token, args.github_issue_repo, args.github_issue 2025-03-14T03:51:43.9896196Z ) 2025-03-14T03:51:43.9896414Z 2025-03-14T03:51:43.9896720Z username = get_potential_pr_author( 2025-03-14T03:51:43.9897291Z args.github_token, 2025-03-14T03:51:43.9897791Z args.github_repo, 2025-03-14T03:51:43.9898283Z args.github_actor, 2025-03-14T03:51:43.9898784Z args.github_ref_type, 2025-03-14T03:51:43.9899298Z args.github_branch, 2025-03-14T03:51:43.9899782Z ) 2025-03-14T03:51:43.9899995Z 2025-03-14T03:51:43.9900283Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-03-14T03:51:43.9900926Z 2025-03-14T03:51:43.9901152Z runner_label_prefix = get_runner_prefix( 2025-03-14T03:51:43.9901729Z rollout_state, 2025-03-14T03:51:43.9902227Z (args.github_issue_owner, username), 2025-03-14T03:51:43.9902811Z args.github_branch, 2025-03-14T03:51:43.9903327Z args.eligible_experiments, 2025-03-14T03:51:43.9903853Z is_canary, 2025-03-14T03:51:43.9904283Z ) 2025-03-14T03:51:43.9904499Z 2025-03-14T03:51:43.9904685Z except Exception as e: 2025-03-14T03:51:43.9905153Z log.error( 2025-03-14T03:51:43.9905851Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-03-14T03:51:43.9906886Z ) 2025-03-14T03:51:43.9907116Z 2025-03-14T03:51:43.9907476Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-03-14T03:51:43.9908015Z 2025-03-14T03:51:43.9908021Z 2025-03-14T03:51:43.9908207Z if __name__ == "__main__": 2025-03-14T03:51:43.9908668Z main() 2025-03-14T03:51:43.9908882Z 2025-03-14T03:51:44.0000829Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-03-14T03:51:44.0001775Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-03-14T03:51:44.0048728Z shell: /usr/bin/bash -e {0} 2025-03-14T03:51:44.0049229Z env: 2025-03-14T03:51:44.0049899Z GITHUB_TOKEN: *** 2025-03-14T03:51:44.0050331Z ISSUE_NUMBER: 5132 2025-03-14T03:51:44.0050797Z TRIGGERING_ACTOR: pytorchmergebot 2025-03-14T03:51:44.0051312Z ISSUE_OWNER: 2025-03-14T03:51:44.0051721Z CHECK_EXPERIMENTS: 2025-03-14T03:51:44.0052152Z PR_NUMBER: 2025-03-14T03:51:44.0052544Z ##[endgroup] 2025-03-14T03:51:45.7718243Z Defaulting to user installation because normal site-packages is not writeable 2025-03-14T03:51:47.0331150Z Collecting urllib3==1.26.18 2025-03-14T03:51:47.1176895Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-03-14T03:51:47.1509951Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.0 MB/s eta 0:00:00 2025-03-14T03:51:47.1763804Z Collecting PyGithub==2.3.0 2025-03-14T03:51:47.1960768Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-03-14T03:51:47.2401221Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-03-14T03:51:47.2596827Z 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:51:47.2646376Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-03-14T03:51:47.2663181Z 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:51:47.2677996Z 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:51:47.2965757Z Collecting Deprecated (from PyGithub==2.3.0) 2025-03-14T03:51:47.3166284Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-03-14T03:51:47.3405681Z 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:51:47.4534721Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-03-14T03:51:47.4731471Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-03-14T03:51:47.5769787Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-03-14T03:51:47.5975203Z 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:51:47.6180135Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-03-14T03:51:47.6429866Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-03-14T03:51:47.6834616Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-03-14T03:51:47.7080805Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 5.9 MB/s eta 0:00:00 2025-03-14T03:51:47.7284918Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-03-14T03:51:47.7524169Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 15.7 MB/s eta 0:00:00 2025-03-14T03:51:47.7719062Z 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:51:47.7971575Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 36.2 MB/s eta 0:00:00 2025-03-14T03:51:47.8167897Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-03-14T03:51:47.8393192Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-03-14T03:51:47.8475427Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 74.8 MB/s eta 0:00:00 2025-03-14T03:51:47.8674230Z 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:51:47.8742910Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 15.3 MB/s eta 0:00:00 2025-03-14T03:51:47.8940474Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-03-14T03:51:47.9001614Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 24.4 MB/s eta 0:00:00 2025-03-14T03:51:48.1954502Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-03-14T03:51:48.7299251Z 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:51:48.8142538Z ##[group]Run curr_branch="main" 2025-03-14T03:51:48.8142885Z curr_branch="main" 2025-03-14T03:51:48.8143125Z curr_ref_type="branch" 2025-03-14T03:51:48.8143387Z echo "Current branch is '$curr_branch'" 2025-03-14T03:51:48.8143652Z  2025-03-14T03:51:48.8143847Z python3 runner_determinator.py \ 2025-03-14T03:51:48.8144138Z  --github-token "$GITHUB_TOKEN" \ 2025-03-14T03:51:48.8144427Z  --github-issue "$ISSUE_NUMBER" \ 2025-03-14T03:51:48.8144717Z  --github-branch "$curr_branch" \ 2025-03-14T03:51:48.8144984Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-03-14T03:51:48.8145273Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-03-14T03:51:48.8145554Z  --github-ref-type "$curr_ref_type" \ 2025-03-14T03:51:48.8145831Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-03-14T03:51:48.8146136Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-03-14T03:51:48.8146793Z  --pr-number "${PR_NUMBER}" 2025-03-14T03:51:48.8194573Z shell: /usr/bin/bash -e {0} 2025-03-14T03:51:48.8194809Z env: 2025-03-14T03:51:48.8195376Z GITHUB_TOKEN: *** 2025-03-14T03:51:48.8195573Z ISSUE_NUMBER: 5132 2025-03-14T03:51:48.8195777Z TRIGGERING_ACTOR: pytorchmergebot 2025-03-14T03:51:48.8196015Z ISSUE_OWNER: 2025-03-14T03:51:48.8196204Z CHECK_EXPERIMENTS: 2025-03-14T03:51:48.8196390Z PR_NUMBER: 2025-03-14T03:51:48.8196809Z ##[endgroup] 2025-03-14T03:51:48.8267003Z Current branch is 'main' 2025-03-14T03:51:50.4970025Z INFO : Based on rollout percentage of 50%, enabling experiment lf. 2025-03-14T03:51:50.4971252Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-03-14T03:51:50.4972009Z INFO : Setting output: label-type='lf.' 2025-03-14T03:51:50.5300747Z Evaluate and set job outputs 2025-03-14T03:51:50.5307723Z Set output 'label-type' 2025-03-14T03:51:50.5309585Z Cleaning up orphan processes