2025-07-17T06:33:19.7292589Z Current runner version: '2.326.0' 2025-07-17T06:33:19.7317886Z ##[group]Runner Image Provisioner 2025-07-17T06:33:19.7318910Z Hosted Compute Agent 2025-07-17T06:33:19.7319533Z Version: 20250711.363 2025-07-17T06:33:19.7320080Z Commit: 6785254374ce925a23743850c1cb91912ce5c14c 2025-07-17T06:33:19.7320871Z Build Date: 2025-07-11T20:04:25Z 2025-07-17T06:33:19.7321463Z ##[endgroup] 2025-07-17T06:33:19.7322051Z ##[group]Operating System 2025-07-17T06:33:19.7322646Z Ubuntu 2025-07-17T06:33:19.7323121Z 24.04.2 2025-07-17T06:33:19.7323549Z LTS 2025-07-17T06:33:19.7324556Z ##[endgroup] 2025-07-17T06:33:19.7325041Z ##[group]Runner Image 2025-07-17T06:33:19.7325633Z Image: ubuntu-24.04 2025-07-17T06:33:19.7326227Z Version: 20250710.1.0 2025-07-17T06:33:19.7327179Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250710.1/images/ubuntu/Ubuntu2404-Readme.md 2025-07-17T06:33:19.7328755Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250710.1 2025-07-17T06:33:19.7329740Z ##[endgroup] 2025-07-17T06:33:19.7330867Z ##[group]GITHUB_TOKEN Permissions 2025-07-17T06:33:19.7332985Z Contents: read 2025-07-17T06:33:19.7333494Z Metadata: read 2025-07-17T06:33:19.7334259Z Packages: read 2025-07-17T06:33:19.7334741Z ##[endgroup] 2025-07-17T06:33:19.7337372Z Secret source: Actions 2025-07-17T06:33:19.7338037Z Prepare workflow directory 2025-07-17T06:33:19.7880009Z Prepare all required actions 2025-07-17T06:33:19.7938417Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (a38f433be2e94a64b095a44ba39879d02d0c2316) 2025-07-17T06:33:19.7943374Z ##[group] Inputs 2025-07-17T06:33:19.7944260Z check_experiments: 2025-07-17T06:33:19.7944922Z opt_out_experiments: 2025-07-17T06:33:19.7945463Z triggering_actor: pytorchmergebot 2025-07-17T06:33:19.7946094Z issue_owner: 2025-07-17T06:33:19.7946648Z curr_branch: main 2025-07-17T06:33:19.7947325Z curr_ref_type: branch 2025-07-17T06:33:19.7948108Z issue_number: 5132 2025-07-17T06:33:19.7948681Z ##[endgroup] 2025-07-17T06:33:19.7949304Z Complete job name: get-label-type / runner-determinator 2025-07-17T06:33:19.8569447Z ##[group]Run cat < runner_determinator.py 2025-07-17T06:33:19.8572194Z cat < runner_determinator.py 2025-07-17T06:33:19.8572897Z # flake8: noqa: G004 2025-07-17T06:33:19.8573473Z  2025-07-17T06:33:19.8574381Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:33:19.8575445Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:33:19.8576320Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:33:19.8577082Z  2025-07-17T06:33:19.8577500Z """ 2025-07-17T06:33:19.8578237Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:33:19.8579298Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:33:19.8580405Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:33:19.8581416Z of which runners should be used to run which job. 2025-07-17T06:33:19.8582058Z  2025-07-17T06:33:19.8582739Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:33:19.8583968Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:33:19.8585118Z settings are considered to be empty with only the second part, the user 2025-07-17T06:33:19.8585912Z list, defined. 2025-07-17T06:33:19.8586373Z  2025-07-17T06:33:19.8587121Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:33:19.8588115Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:33:19.8589093Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:33:19.8590210Z  2025-07-17T06:33:19.8590861Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:33:19.8591818Z The user list is also a comma separated list of additional features or 2025-07-17T06:33:19.8592719Z experiments which the user could be opted in to. 2025-07-17T06:33:19.8593461Z  2025-07-17T06:33:19.8594081Z The user list has the following rules: 2025-07-17T06:33:19.8594727Z  2025-07-17T06:33:19.8595359Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:33:19.8596281Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:33:19.8597213Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:33:19.8597857Z  2025-07-17T06:33:19.8598316Z Example config: 2025-07-17T06:33:19.8598977Z  # A list of experiments that can be opted into. 2025-07-17T06:33:19.8599748Z  # This defines the behavior they'll induce when opted into. 2025-07-17T06:33:19.8600479Z  # Expected syntax is: 2025-07-17T06:33:19.8601253Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:33:19.8602380Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:33:19.8603209Z  2025-07-17T06:33:19.8603769Z  experiments: 2025-07-17T06:33:19.8604466Z  lf: 2025-07-17T06:33:19.8604973Z  rollout_percent: 25 2025-07-17T06:33:19.8606006Z  all_branches: false 2025-07-17T06:33:19.8606688Z  default: true 2025-07-17T06:33:19.8607243Z  --- 2025-07-17T06:33:19.8607751Z  2025-07-17T06:33:19.8608179Z  # Opt-ins: 2025-07-17T06:33:19.8608910Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:33:19.8610125Z  # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:33:19.8611095Z  # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:33:19.8611845Z  # Experiments should be from the above list. 2025-07-17T06:33:19.8612525Z  2025-07-17T06:33:19.8613012Z  @User1,-lf,split_build 2025-07-17T06:33:19.8613553Z  @User2,lf 2025-07-17T06:33:19.8614371Z  @User3,split_build 2025-07-17T06:33:19.8614949Z """ 2025-07-17T06:33:19.8615420Z  2025-07-17T06:33:19.8615828Z import json 2025-07-17T06:33:19.8616415Z import logging 2025-07-17T06:33:19.8617028Z import os 2025-07-17T06:33:19.8617486Z import random 2025-07-17T06:33:19.8618176Z import re 2025-07-17T06:33:19.8618634Z import sys 2025-07-17T06:33:19.8619231Z from argparse import ArgumentParser 2025-07-17T06:33:19.8620000Z from collections.abc import Iterable 2025-07-17T06:33:19.8620634Z from functools import cache 2025-07-17T06:33:19.8621305Z from logging import LogRecord 2025-07-17T06:33:19.8621963Z from typing import Any, NamedTuple 2025-07-17T06:33:19.8622681Z from urllib.request import Request, urlopen 2025-07-17T06:33:19.8623275Z  2025-07-17T06:33:19.8624037Z import yaml 2025-07-17T06:33:19.8624612Z from github import Auth, Github 2025-07-17T06:33:19.8625184Z from github.Issue import Issue 2025-07-17T06:33:19.8625901Z  2025-07-17T06:33:19.8626305Z  2025-07-17T06:33:19.8626833Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:33:19.8627737Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:33:19.8628723Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:33:19.8629747Z  2025-07-17T06:33:19.8630395Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:33:19.8631100Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:33:19.8631733Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:33:19.8632488Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:33:19.8633079Z  2025-07-17T06:33:19.8633578Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:33:19.8634434Z  2025-07-17T06:33:19.8634878Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:33:19.8635464Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:33:19.8636054Z  2025-07-17T06:33:19.8636503Z  2025-07-17T06:33:19.8636945Z class Experiment(NamedTuple): 2025-07-17T06:33:19.8637649Z  rollout_perc: float = ( 2025-07-17T06:33:19.8638442Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:33:19.8639217Z  ) 2025-07-17T06:33:19.8639799Z  all_branches: bool = ( 2025-07-17T06:33:19.8640561Z  False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:33:19.8641324Z  ) 2025-07-17T06:33:19.8641881Z  default: bool = ( 2025-07-17T06:33:19.8642615Z  True # If True, the experiment is enabled by default for all queries 2025-07-17T06:33:19.8643335Z  ) 2025-07-17T06:33:19.8644020Z  2025-07-17T06:33:19.8644636Z  # Add more fields as needed 2025-07-17T06:33:19.8645190Z  2025-07-17T06:33:19.8645730Z  2025-07-17T06:33:19.8646182Z class Settings(NamedTuple): 2025-07-17T06:33:19.8646759Z  """ 2025-07-17T06:33:19.8647409Z  Settings for the experiments that can be opted into. 2025-07-17T06:33:19.8648057Z  """ 2025-07-17T06:33:19.8648522Z  2025-07-17T06:33:19.8649065Z  experiments: dict[str, Experiment] = {} 2025-07-17T06:33:19.8649712Z  2025-07-17T06:33:19.8650281Z  2025-07-17T06:33:19.8650837Z class ColorFormatter(logging.Formatter): 2025-07-17T06:33:19.8651566Z  """Color codes the log messages based on the log level""" 2025-07-17T06:33:19.8652291Z  2025-07-17T06:33:19.8652819Z  COLORS = { 2025-07-17T06:33:19.8653349Z  "WARNING": "\033[33m", # Yellow 2025-07-17T06:33:19.8654181Z  "ERROR": "\033[31m", # Red 2025-07-17T06:33:19.8654822Z  "CRITICAL": "\033[31m", # Red 2025-07-17T06:33:19.8655473Z  "INFO": "\033[0m", # Reset 2025-07-17T06:33:19.8656065Z  "DEBUG": "\033[0m", # Reset 2025-07-17T06:33:19.8656734Z  } 2025-07-17T06:33:19.8657212Z  2025-07-17T06:33:19.8657709Z  def format(self, record: LogRecord) -> str: 2025-07-17T06:33:19.8658627Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:33:19.8659517Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:33:19.8660250Z  return super().format(record) 2025-07-17T06:33:19.8660890Z  2025-07-17T06:33:19.8661318Z  2025-07-17T06:33:19.8661815Z handler = logging.StreamHandler() 2025-07-17T06:33:19.8662617Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:33:19.8663504Z  2025-07-17T06:33:19.8664325Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:33:19.8665151Z log.addHandler(handler) 2025-07-17T06:33:19.8665769Z log.setLevel(logging.INFO) 2025-07-17T06:33:19.8666297Z  2025-07-17T06:33:19.8666781Z  2025-07-17T06:33:19.8667338Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:33:19.8668072Z  """ 2025-07-17T06:33:19.8668672Z  Defines outputs of the github action that invokes this script 2025-07-17T06:33:19.8669673Z  """ 2025-07-17T06:33:19.8670135Z  if not GITHUB_OUTPUT: 2025-07-17T06:33:19.8671309Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:33:19.8672570Z  log.warning( 2025-07-17T06:33:19.8673542Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-07-17T06:33:19.8675160Z  ) 2025-07-17T06:33:19.8675844Z  print(f"::set-output name={key}::{value}") 2025-07-17T06:33:19.8676461Z  return 2025-07-17T06:33:19.8677009Z  2025-07-17T06:33:19.8677497Z  with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:33:19.8678236Z  log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:33:19.8678874Z  f.write(f"{key}={value}\n") 2025-07-17T06:33:19.8679551Z  2025-07-17T06:33:19.8679994Z  2025-07-17T06:33:19.8680570Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:33:19.8681394Z  return frozenset( 2025-07-17T06:33:19.8682149Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:33:19.8682911Z  ) 2025-07-17T06:33:19.8683402Z  2025-07-17T06:33:19.8684123Z  2025-07-17T06:33:19.8684579Z def parse_args() -> Any: 2025-07-17T06:33:19.8685361Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:33:19.8686380Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:33:19.8687183Z  parser.add_argument( 2025-07-17T06:33:19.8687877Z  "--github-issue-repo", 2025-07-17T06:33:19.8688446Z  type=str, 2025-07-17T06:33:19.8688990Z  required=False, 2025-07-17T06:33:19.8690058Z  default="pytorch/test-infra", 2025-07-17T06:33:19.8690780Z  help="GitHub repo to get the issue", 2025-07-17T06:33:19.8691377Z  ) 2025-07-17T06:33:19.8691907Z  parser.add_argument( 2025-07-17T06:33:19.8692492Z  "--github-repo", 2025-07-17T06:33:19.8693016Z  type=str, 2025-07-17T06:33:19.8693620Z  required=True, 2025-07-17T06:33:19.8694540Z  help="GitHub repo where CI is running", 2025-07-17T06:33:19.8695118Z  ) 2025-07-17T06:33:19.8695692Z  parser.add_argument( 2025-07-17T06:33:19.8696422Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:33:19.8697172Z  ) 2025-07-17T06:33:19.8697702Z  parser.add_argument( 2025-07-17T06:33:19.8698509Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:33:19.8699277Z  ) 2025-07-17T06:33:19.8699798Z  parser.add_argument( 2025-07-17T06:33:19.8700584Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:33:19.8701333Z  ) 2025-07-17T06:33:19.8701904Z  parser.add_argument( 2025-07-17T06:33:19.8702673Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:33:19.8703505Z  ) 2025-07-17T06:33:19.8704291Z  parser.add_argument( 2025-07-17T06:33:19.8704898Z  "--github-ref-type", 2025-07-17T06:33:19.8774246Z  type=str, 2025-07-17T06:33:19.8775345Z  required=True, 2025-07-17T06:33:19.8776261Z  help="Current GitHub ref type, branch or tag", 2025-07-17T06:33:19.8777229Z  ) 2025-07-17T06:33:19.8777899Z  parser.add_argument( 2025-07-17T06:33:19.8779329Z  "--eligible-experiments", 2025-07-17T06:33:19.8780102Z  type=_str_comma_separated_to_set, 2025-07-17T06:33:19.8780655Z  required=False, 2025-07-17T06:33:19.8781108Z  default="", 2025-07-17T06:33:19.8781982Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:33:19.8782881Z  ) 2025-07-17T06:33:19.8783279Z  parser.add_argument( 2025-07-17T06:33:19.8784016Z  "--opt-out-experiments", 2025-07-17T06:33:19.8784601Z  type=_str_comma_separated_to_set, 2025-07-17T06:33:19.8785128Z  required=False, 2025-07-17T06:33:19.8785582Z  default="", 2025-07-17T06:33:19.8786019Z  help=( 2025-07-17T06:33:19.8786709Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:33:19.8787822Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:33:19.8788646Z  ), 2025-07-17T06:33:19.8789032Z  ) 2025-07-17T06:33:19.8789431Z  parser.add_argument( 2025-07-17T06:33:19.8789918Z  "--pr-number", 2025-07-17T06:33:19.8790375Z  type=str, 2025-07-17T06:33:19.8790823Z  required=False, 2025-07-17T06:33:19.8791291Z  default="", 2025-07-17T06:33:19.8791825Z  help="the optional PR number where this is run", 2025-07-17T06:33:19.8792382Z  ) 2025-07-17T06:33:19.8792747Z  2025-07-17T06:33:19.8793134Z  return parser.parse_args() 2025-07-17T06:33:19.8793624Z  2025-07-17T06:33:19.8794209Z  2025-07-17T06:33:19.8794824Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:33:19.8795781Z  auth = Auth.Token(github_token) 2025-07-17T06:33:19.8796329Z  return Github(auth=auth) 2025-07-17T06:33:19.8796790Z  2025-07-17T06:33:19.8797133Z  2025-07-17T06:33:19.8797795Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:33:19.8798582Z  repo = gh.get_repo(repo) 2025-07-17T06:33:19.8799120Z  return repo.get_issue(number=issue_num) 2025-07-17T06:33:19.8799634Z  2025-07-17T06:33:19.8799968Z  2025-07-17T06:33:19.8800339Z def get_potential_pr_author( 2025-07-17T06:33:19.8801016Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:33:19.8801674Z ) -> str: 2025-07-17T06:33:19.8802228Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:33:19.8803053Z  # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:33:19.8804013Z  # embedded in the tag name: ciflow// 2025-07-17T06:33:19.8804587Z  2025-07-17T06:33:19.8804982Z  gh = get_gh_client(github_token) 2025-07-17T06:33:19.8805475Z  2025-07-17T06:33:19.8805959Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:33:19.8806598Z  split_tag = ref_name.split("/") 2025-07-17T06:33:19.8807098Z  if ( 2025-07-17T06:33:19.8807513Z  len(split_tag) == 3 2025-07-17T06:33:19.8808028Z  and split_tag[0] == "ciflow" 2025-07-17T06:33:19.8808563Z  and split_tag[2].isnumeric() 2025-07-17T06:33:19.8809062Z  ): 2025-07-17T06:33:19.8809484Z  pr_number = split_tag[2] 2025-07-17T06:33:19.8809980Z  try: 2025-07-17T06:33:19.8810453Z  repository = gh.get_repo(repo) 2025-07-17T06:33:19.8811238Z  pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:33:19.8811859Z  except Exception as e: 2025-07-17T06:33:19.8812401Z  raise Exception( # noqa: TRY002 2025-07-17T06:33:19.8813083Z  f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:33:19.8814494Z  ) from e 2025-07-17T06:33:19.8815137Z  return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:33:19.8815851Z  # In all other cases, return the original input username 2025-07-17T06:33:19.8816444Z  return username 2025-07-17T06:33:19.8816876Z  2025-07-17T06:33:19.8817205Z  2025-07-17T06:33:19.8817632Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:33:19.8818161Z  """ 2025-07-17T06:33:19.8818832Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:33:19.8819603Z  """ 2025-07-17T06:33:19.8820170Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:33:19.8820823Z  2025-07-17T06:33:19.8821157Z  2025-07-17T06:33:19.8821556Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:33:19.8822053Z  try: 2025-07-17T06:33:19.8822469Z  data = yaml.safe_load(yaml_text) 2025-07-17T06:33:19.8822979Z  return data 2025-07-17T06:33:19.8823437Z  except yaml.YAMLError: 2025-07-17T06:33:19.8824508Z  log.exception("Error loading YAML") 2025-07-17T06:33:19.8825022Z  raise 2025-07-17T06:33:19.8825422Z  2025-07-17T06:33:19.8825749Z  2025-07-17T06:33:19.8826365Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:33:19.8827093Z  """ 2025-07-17T06:33:19.8827913Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:33:19.8828653Z  2025-07-17T06:33:19.8829193Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:19.8829956Z  and the text below is the list of opted in users. 2025-07-17T06:33:19.8830516Z  2025-07-17T06:33:19.8831091Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:33:19.8831764Z  """ 2025-07-17T06:33:19.8832242Z  rollout_state_parts = rollout_state.split("---") 2025-07-17T06:33:19.8832852Z  if len(rollout_state_parts) >= 2: 2025-07-17T06:33:19.8833487Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:33:19.8834421Z  else: 2025-07-17T06:33:19.8834831Z  return "", rollout_state 2025-07-17T06:33:19.8835305Z  2025-07-17T06:33:19.8835649Z  2025-07-17T06:33:19.8836063Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:33:19.8836575Z  """ 2025-07-17T06:33:19.8837128Z  Dictionary of users with a list of features they have opted into 2025-07-17T06:33:19.8837752Z  """ 2025-07-17T06:33:19.8838103Z  2025-07-17T06:33:19.8838434Z  2025-07-17T06:33:19.8839016Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:33:19.8839663Z  """ 2025-07-17T06:33:19.8840381Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-07-17T06:33:19.8841186Z  2025-07-17T06:33:19.8841977Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:33:19.8842946Z  - Example line: "@User1,lf,split_build" 2025-07-17T06:33:19.8843992Z  - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:33:19.8844612Z  2025-07-17T06:33:19.8844943Z  2025-07-17T06:33:19.8845273Z  """ 2025-07-17T06:33:19.8845666Z  optins = UserOptins() 2025-07-17T06:33:19.8846198Z  for user in user_optin_text.split("\n"): 2025-07-17T06:33:19.8846768Z  user = user.strip("\r\n\t -") 2025-07-17T06:33:19.8847344Z  if not user or not user.startswith("@"): 2025-07-17T06:33:19.8847900Z  # Not a valid user. Skip 2025-07-17T06:33:19.8848403Z  continue 2025-07-17T06:33:19.8848820Z  2025-07-17T06:33:19.8849164Z  if user: 2025-07-17T06:33:19.8849648Z  usr_name = user.split(",")[0].strip("@") 2025-07-17T06:33:19.8850335Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:33:19.8850971Z  2025-07-17T06:33:19.8851324Z  return optins 2025-07-17T06:33:19.8851734Z  2025-07-17T06:33:19.8852061Z  2025-07-17T06:33:19.8852557Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:33:19.8853157Z  """ 2025-07-17T06:33:19.8853588Z  Check if the experiment name is valid. 2025-07-17T06:33:19.8854359Z  A valid name: 2025-07-17T06:33:19.8855038Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:33:19.8855967Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:33:19.8856655Z  - Cannot contain spaces 2025-07-17T06:33:19.8857131Z  """ 2025-07-17T06:33:19.8857492Z  2025-07-17T06:33:19.8857959Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:33:19.8858677Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:33:19.8859396Z  2025-07-17T06:33:19.8859762Z  if valid: 2025-07-17T06:33:19.8860168Z  return True 2025-07-17T06:33:19.8860588Z  2025-07-17T06:33:19.8860925Z  log.error( 2025-07-17T06:33:19.8862311Z  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-07-17T06:33:19.8864016Z  ) 2025-07-17T06:33:19.8864388Z  return False 2025-07-17T06:33:19.8864791Z  2025-07-17T06:33:19.8865115Z  2025-07-17T06:33:19.8865628Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:33:19.8866235Z  """ 2025-07-17T06:33:19.8866835Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:33:19.8867537Z  """ 2025-07-17T06:33:19.8867891Z  try: 2025-07-17T06:33:19.8868276Z  if settings_text: 2025-07-17T06:33:19.8869013Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:33:19.8869781Z  # for easy reading 2025-07-17T06:33:19.8870571Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:33:19.8871444Z  # the backtick character in shell commands. 2025-07-17T06:33:19.8872061Z  backtick = chr(96) # backtick character 2025-07-17T06:33:19.8872722Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:33:19.8873388Z  settings = load_yaml(settings_text) 2025-07-17T06:33:19.8874045Z  2025-07-17T06:33:19.8874780Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:33:19.8875500Z  experiments = {} 2025-07-17T06:33:19.8875949Z  2025-07-17T06:33:19.8876510Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:33:19.8877258Z  if not is_valid_experiment_name(exp_name): 2025-07-17T06:33:19.8878325Z  # 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-07-17T06:33:19.8879314Z  continue 2025-07-17T06:33:19.8879762Z  2025-07-17T06:33:19.8880141Z  valid_settings = {} 2025-07-17T06:33:19.8880672Z  for setting in exp_settings: 2025-07-17T06:33:19.8881254Z  if setting not in Experiment._fields: 2025-07-17T06:33:19.8881814Z  log.warning( 2025-07-17T06:33:19.8882533Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:33:19.8883227Z  ) 2025-07-17T06:33:19.8883778Z  else: 2025-07-17T06:33:19.8884333Z  valid_settings[setting] = exp_settings[setting] 2025-07-17T06:33:19.8884891Z  2025-07-17T06:33:19.8885367Z  experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:33:19.8885997Z  return Settings(experiments) 2025-07-17T06:33:19.8886495Z  2025-07-17T06:33:19.8886860Z  except Exception: 2025-07-17T06:33:19.8887377Z  log.exception("Failed to parse settings") 2025-07-17T06:33:19.8887910Z  2025-07-17T06:33:19.8888259Z  return Settings() 2025-07-17T06:33:19.8888685Z  2025-07-17T06:33:19.8889017Z  2025-07-17T06:33:19.8889618Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:33:19.8890185Z  """ 2025-07-17T06:33:19.8890646Z  Parse settings, if any, from the rollout state. 2025-07-17T06:33:19.8891189Z  2025-07-17T06:33:19.8891720Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:19.8892469Z  and the text below is the list of opted in users. 2025-07-17T06:33:19.8893004Z  2025-07-17T06:33:19.8893598Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:33:19.8894567Z  """ 2025-07-17T06:33:19.8895133Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:19.8895892Z  return parse_settings_from_text(settings_text) 2025-07-17T06:33:19.8896418Z  2025-07-17T06:33:19.8896752Z  2025-07-17T06:33:19.8897197Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:33:19.8897752Z  """ 2025-07-17T06:33:19.8898158Z  Parse users from the rollout state. 2025-07-17T06:33:19.8898655Z  2025-07-17T06:33:19.8898984Z  """ 2025-07-17T06:33:19.8899529Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:19.8900269Z  return parse_user_opt_in_from_text(users_text) 2025-07-17T06:33:19.8900799Z  2025-07-17T06:33:19.8901130Z  2025-07-17T06:33:19.8901738Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:19.8902464Z  """ 2025-07-17T06:33:19.8902906Z  Check if a user is opted into an experiment 2025-07-17T06:33:19.8903424Z  """ 2025-07-17T06:33:19.8904126Z  return experiment_name in user_optins.get(user, []) 2025-07-17T06:33:19.8904872Z  2025-07-17T06:33:19.8905204Z  2025-07-17T06:33:19.8905813Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:19.8906543Z  """ 2025-07-17T06:33:19.8907022Z  Check if a user explicitly opted out of an experiment 2025-07-17T06:33:19.8907588Z  """ 2025-07-17T06:33:19.8908120Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:33:19.8908812Z  experiment_optout = "-" + experiment_name 2025-07-17T06:33:19.8909472Z  if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:33:19.8910063Z  return False 2025-07-17T06:33:19.8910478Z  2025-07-17T06:33:19.8910939Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:33:19.8911529Z  log.warning( 2025-07-17T06:33:19.8912335Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:33:19.8913164Z  ) 2025-07-17T06:33:19.8913528Z  2025-07-17T06:33:19.8913991Z  return True 2025-07-17T06:33:19.8914392Z  2025-07-17T06:33:19.8914719Z  2025-07-17T06:33:19.8915074Z def get_runner_prefix( 2025-07-17T06:33:19.8915535Z  rollout_state: str, 2025-07-17T06:33:19.8916037Z  workflow_requestors: Iterable[str], 2025-07-17T06:33:19.8916546Z  branch: str, 2025-07-17T06:33:19.8917069Z  eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:19.8917751Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:19.8918330Z  is_canary: bool = False, 2025-07-17T06:33:19.8918804Z ) -> str: 2025-07-17T06:33:19.8919257Z  settings = parse_settings(rollout_state) 2025-07-17T06:33:19.8919862Z  user_optins = parse_users(rollout_state) 2025-07-17T06:33:19.8920386Z  2025-07-17T06:33:19.8920868Z  fleet_prefix = "" 2025-07-17T06:33:19.8921325Z  prefixes = [] 2025-07-17T06:33:19.8922010Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:33:19.8922927Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:33:19.8923618Z  log.info( 2025-07-17T06:33:19.8924419Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:33:19.8925142Z  ) 2025-07-17T06:33:19.8925542Z  continue 2025-07-17T06:33:19.8925956Z  2025-07-17T06:33:19.8926317Z  if opt_out_experiments: 2025-07-17T06:33:19.8926876Z  if experiment_name in opt_out_experiments: 2025-07-17T06:33:19.8927509Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:33:19.8928093Z  log.info( 2025-07-17T06:33:19.8929004Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:33:19.8929921Z  ) 2025-07-17T06:33:19.8930343Z  continue 2025-07-17T06:33:19.8930774Z  2025-07-17T06:33:19.8931147Z  if eligible_experiments: 2025-07-17T06:33:19.8931731Z  if experiment_name not in eligible_experiments: 2025-07-17T06:33:19.8932369Z  exp_list = ", ".join(eligible_experiments) 2025-07-17T06:33:19.8932938Z  log.info( 2025-07-17T06:33:19.8933819Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:33:19.8934783Z  ) 2025-07-17T06:33:19.8935350Z  continue 2025-07-17T06:33:19.8935869Z  elif not experiment_settings.default: 2025-07-17T06:33:19.8936401Z  log.info( 2025-07-17T06:33:19.8937070Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:33:19.8937778Z  ) 2025-07-17T06:33:19.8938172Z  continue 2025-07-17T06:33:19.8938591Z  2025-07-17T06:33:19.8939098Z  # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:33:19.8939703Z  opted_out_users = [ 2025-07-17T06:33:19.8940181Z  requestor 2025-07-17T06:33:19.8940672Z  for requestor in workflow_requestors 2025-07-17T06:33:19.8941351Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:33:19.8941957Z  ] 2025-07-17T06:33:19.8942326Z  2025-07-17T06:33:19.8942683Z  if opted_out_users: 2025-07-17T06:33:19.8943163Z  log.info( 2025-07-17T06:33:19.8944104Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:33:19.8944791Z  ) 2025-07-17T06:33:19.8945193Z  continue 2025-07-17T06:33:19.8945603Z  2025-07-17T06:33:19.8946072Z  # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:33:19.8946669Z  opted_in_users = [ 2025-07-17T06:33:19.8947144Z  requestor 2025-07-17T06:33:19.8947639Z  for requestor in workflow_requestors 2025-07-17T06:33:19.8948501Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:33:19.8949210Z  ] 2025-07-17T06:33:19.8949578Z  2025-07-17T06:33:19.8949937Z  enabled = False 2025-07-17T06:33:19.8950411Z  if opted_in_users: 2025-07-17T06:33:19.8951026Z  log.info( 2025-07-17T06:33:19.8951659Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:33:19.8952317Z  ) 2025-07-17T06:33:19.8952724Z  enabled = True 2025-07-17T06:33:19.8953161Z  2025-07-17T06:33:19.8953572Z  elif experiment_settings.rollout_perc: 2025-07-17T06:33:19.8954507Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:33:19.8955428Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:33:19.8956054Z  log.info( 2025-07-17T06:33:19.8956920Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:33:19.8957803Z  ) 2025-07-17T06:33:19.8958235Z  enabled = True 2025-07-17T06:33:19.8958694Z  2025-07-17T06:33:19.8959043Z  if enabled: 2025-07-17T06:33:19.8959507Z  label = experiment_name 2025-07-17T06:33:19.8960066Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:33:19.8960873Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:33:19.8961722Z  # - If it's enabled, then we always list it's prefix first 2025-07-17T06:33:19.8962464Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:33:19.8963112Z  if is_canary: 2025-07-17T06:33:19.8963624Z  label += CANARY_FLEET_SUFFIX 2025-07-17T06:33:19.8964347Z  fleet_prefix = label 2025-07-17T06:33:19.8964980Z  else: 2025-07-17T06:33:19.8965441Z  prefixes.append(label) 2025-07-17T06:33:19.8965934Z  2025-07-17T06:33:19.8966287Z  if len(prefixes) > 1: 2025-07-17T06:33:19.8966748Z  log.error( 2025-07-17T06:33:19.8967756Z  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-07-17T06:33:19.8968806Z  ) 2025-07-17T06:33:19.8969218Z  prefixes = prefixes[:1] 2025-07-17T06:33:19.8969685Z  2025-07-17T06:33:19.8970064Z  # Fleet always comes first 2025-07-17T06:33:19.8970543Z  if fleet_prefix: 2025-07-17T06:33:19.8971024Z  prefixes.insert(0, fleet_prefix) 2025-07-17T06:33:19.8971515Z  2025-07-17T06:33:19.8971964Z  return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:33:19.8972515Z  2025-07-17T06:33:19.8972855Z  2025-07-17T06:33:19.8973485Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:33:19.8974469Z  """ 2025-07-17T06:33:19.8975073Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:33:19.8975750Z  2025-07-17T06:33:19.8976331Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:33:19.8977004Z  """ 2025-07-17T06:33:19.8977418Z  gh = get_gh_client(github_token) 2025-07-17T06:33:19.8977978Z  issue = get_issue(gh, repo, issue_num) 2025-07-17T06:33:19.8978622Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:33:19.8979207Z  2025-07-17T06:33:19.8979535Z  2025-07-17T06:33:19.8980127Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:33:19.8981000Z  for _ in range(num_retries): 2025-07-17T06:33:19.8981487Z  try: 2025-07-17T06:33:19.8981942Z  req = Request(url=url, headers=headers) 2025-07-17T06:33:19.8982597Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:33:19.8983239Z  return json.loads(content) 2025-07-17T06:33:19.8983992Z  except Exception as e: 2025-07-17T06:33:19.8984572Z  log.warning(f"Could not download {url}: {e}") 2025-07-17T06:33:19.8985103Z  2025-07-17T06:33:19.8985667Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:33:19.8986359Z  return {} 2025-07-17T06:33:19.8986747Z  2025-07-17T06:33:19.8987084Z  2025-07-17T06:33:19.8987412Z @cache 2025-07-17T06:33:19.8988049Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:33:19.8988771Z  """ 2025-07-17T06:33:19.8989183Z  Dynamically get PR information 2025-07-17T06:33:19.8989659Z  """ 2025-07-17T06:33:19.8990170Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:33:19.8990784Z  headers = { 2025-07-17T06:33:19.8991271Z  "Accept": "application/vnd.github.v3+json", 2025-07-17T06:33:19.8991883Z  "Authorization": f"token {github_token}", 2025-07-17T06:33:19.8992400Z  } 2025-07-17T06:33:19.8992855Z  json_response: dict[str, Any] = download_json( 2025-07-17T06:33:19.8993456Z  url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:33:19.8994105Z  headers=headers, 2025-07-17T06:33:19.8994550Z  ) 2025-07-17T06:33:19.8994897Z  2025-07-17T06:33:19.8995253Z  if not json_response: 2025-07-17T06:33:19.8996009Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:33:19.8996626Z  return {} 2025-07-17T06:33:19.8997028Z  2025-07-17T06:33:19.8997393Z  return json_response 2025-07-17T06:33:19.8997835Z  2025-07-17T06:33:19.8998165Z  2025-07-17T06:33:19.8998755Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:33:19.8999448Z  """ 2025-07-17T06:33:19.8999995Z  Dynamically get the latest list of labels from the pull request 2025-07-17T06:33:19.9000616Z  """ 2025-07-17T06:33:19.9001117Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:33:19.9001703Z  return { 2025-07-17T06:33:19.9002294Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:33:19.9002956Z  } 2025-07-17T06:33:19.9003414Z  2025-07-17T06:33:19.9003939Z  2025-07-17T06:33:19.9004296Z def main() -> None: 2025-07-17T06:33:19.9004745Z  args = parse_args() 2025-07-17T06:33:19.9005178Z  2025-07-17T06:33:19.9005592Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:33:19.9006109Z  2025-07-17T06:33:19.9006479Z  # Check if the PR is opt-out 2025-07-17T06:33:19.9006980Z  if args.pr_number: 2025-07-17T06:33:19.9007649Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:33:19.9008380Z  if OPT_OUT_LABEL in labels: 2025-07-17T06:33:19.9008879Z  log.info( 2025-07-17T06:33:19.9009576Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:33:19.9010299Z  ) 2025-07-17T06:33:19.9010872Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:19.9011534Z  sys.exit() 2025-07-17T06:33:19.9012110Z  2025-07-17T06:33:19.9012459Z  try: 2025-07-17T06:33:19.9012911Z  rollout_state = get_rollout_state_from_issue( 2025-07-17T06:33:19.9013616Z  args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:33:19.9014499Z  ) 2025-07-17T06:33:19.9014872Z  2025-07-17T06:33:19.9015274Z  username = get_potential_pr_author( 2025-07-17T06:33:19.9015810Z  args.github_token, 2025-07-17T06:33:19.9016308Z  args.github_repo, 2025-07-17T06:33:19.9016791Z  args.github_actor, 2025-07-17T06:33:19.9017296Z  args.github_ref_type, 2025-07-17T06:33:19.9017798Z  args.github_branch, 2025-07-17T06:33:19.9018265Z  ) 2025-07-17T06:33:19.9018626Z  2025-07-17T06:33:19.9019111Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:33:19.9019695Z  2025-07-17T06:33:19.9020108Z  runner_label_prefix = get_runner_prefix( 2025-07-17T06:33:19.9020652Z  rollout_state, 2025-07-17T06:33:19.9021159Z  (args.github_issue_owner, username), 2025-07-17T06:33:19.9021705Z  args.github_branch, 2025-07-17T06:33:19.9022213Z  args.eligible_experiments, 2025-07-17T06:33:19.9022753Z  args.opt_out_experiments, 2025-07-17T06:33:19.9023258Z  is_canary, 2025-07-17T06:33:19.9023889Z  ) 2025-07-17T06:33:19.9024306Z  2025-07-17T06:33:19.9024673Z  except Exception as e: 2025-07-17T06:33:19.9025145Z  log.error( 2025-07-17T06:33:19.9025825Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:33:19.9026710Z  ) 2025-07-17T06:33:19.9027077Z  2025-07-17T06:33:19.9027598Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:19.9028219Z  2025-07-17T06:33:19.9028541Z  2025-07-17T06:33:19.9028891Z if __name__ == "__main__": 2025-07-17T06:33:19.9029339Z  main() 2025-07-17T06:33:19.9029712Z  2025-07-17T06:33:19.9030041Z EOF 2025-07-17T06:33:19.9030383Z  2025-07-17T06:33:19.9030749Z cat runner_determinator.py 2025-07-17T06:33:19.9237055Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:19.9238284Z env: 2025-07-17T06:33:19.9238994Z GITHUB_TOKEN: *** 2025-07-17T06:33:19.9239405Z ISSUE_NUMBER: 5132 2025-07-17T06:33:19.9239840Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:19.9240319Z ISSUE_OWNER: 2025-07-17T06:33:19.9240711Z CHECK_EXPERIMENTS: 2025-07-17T06:33:19.9241124Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:19.9241564Z PR_NUMBER: 2025-07-17T06:33:19.9241917Z ##[endgroup] 2025-07-17T06:33:19.9504996Z # flake8: noqa: G004 2025-07-17T06:33:19.9505553Z 2025-07-17T06:33:19.9506288Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:33:19.9507942Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:33:19.9509309Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:33:19.9510105Z 2025-07-17T06:33:19.9510374Z """ 2025-07-17T06:33:19.9511350Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:33:19.9512646Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:33:19.9513518Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:33:19.9514739Z of which runners should be used to run which job. 2025-07-17T06:33:19.9515125Z 2025-07-17T06:33:19.9515497Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:33:19.9516621Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:33:19.9517463Z settings are considered to be empty with only the second part, the user 2025-07-17T06:33:19.9518097Z list, defined. 2025-07-17T06:33:19.9518313Z 2025-07-17T06:33:19.9518651Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:33:19.9519497Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:33:19.9520256Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:33:19.9520672Z 2025-07-17T06:33:19.9521013Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:33:19.9521806Z The user list is also a comma separated list of additional features or 2025-07-17T06:33:19.9522489Z experiments which the user could be opted in to. 2025-07-17T06:33:19.9522861Z 2025-07-17T06:33:19.9523055Z The user list has the following rules: 2025-07-17T06:33:19.9523380Z 2025-07-17T06:33:19.9523853Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:33:19.9524710Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:33:19.9525421Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:33:19.9525799Z 2025-07-17T06:33:19.9525955Z Example config: 2025-07-17T06:33:19.9526386Z # A list of experiments that can be opted into. 2025-07-17T06:33:19.9527029Z # This defines the behavior they'll induce when opted into. 2025-07-17T06:33:19.9527612Z # Expected syntax is: 2025-07-17T06:33:19.9528216Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:33:19.9529124Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:33:19.9529688Z 2025-07-17T06:33:19.9529846Z experiments: 2025-07-17T06:33:19.9530218Z lf: 2025-07-17T06:33:19.9530570Z rollout_percent: 25 2025-07-17T06:33:19.9531172Z all_branches: false 2025-07-17T06:33:19.9531957Z default: true 2025-07-17T06:33:19.9532418Z --- 2025-07-17T06:33:19.9532609Z 2025-07-17T06:33:19.9532767Z # Opt-ins: 2025-07-17T06:33:19.9533323Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:33:19.9534385Z # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:33:19.9535114Z # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:33:19.9535725Z # Experiments should be from the above list. 2025-07-17T06:33:19.9536073Z 2025-07-17T06:33:19.9536242Z @User1,-lf,split_build 2025-07-17T06:33:19.9536646Z @User2,lf 2025-07-17T06:33:19.9537005Z @User3,split_build 2025-07-17T06:33:19.9537383Z """ 2025-07-17T06:33:19.9537559Z 2025-07-17T06:33:19.9537716Z import json 2025-07-17T06:33:19.9538059Z import logging 2025-07-17T06:33:19.9538417Z import os 2025-07-17T06:33:19.9538752Z import random 2025-07-17T06:33:19.9539119Z import re 2025-07-17T06:33:19.9539454Z import sys 2025-07-17T06:33:19.9539840Z from argparse import ArgumentParser 2025-07-17T06:33:19.9540334Z from collections.abc import Iterable 2025-07-17T06:33:19.9540822Z from functools import cache 2025-07-17T06:33:19.9541266Z from logging import LogRecord 2025-07-17T06:33:19.9541722Z from typing import Any, NamedTuple 2025-07-17T06:33:19.9542223Z from urllib.request import Request, urlopen 2025-07-17T06:33:19.9542562Z 2025-07-17T06:33:19.9542708Z import yaml 2025-07-17T06:33:19.9543080Z from github import Auth, Github 2025-07-17T06:33:19.9543530Z from github.Issue import Issue 2025-07-17T06:33:19.9544049Z 2025-07-17T06:33:19.9544057Z 2025-07-17T06:33:19.9544269Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:33:19.9544905Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:33:19.9545703Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:33:19.9546213Z 2025-07-17T06:33:19.9546447Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:33:19.9547147Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:33:19.9547646Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:33:19.9548161Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:33:19.9548497Z 2025-07-17T06:33:19.9548684Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:33:19.9548993Z 2025-07-17T06:33:19.9549169Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:33:19.9549607Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:33:19.9549872Z 2025-07-17T06:33:19.9549878Z 2025-07-17T06:33:19.9550065Z class Experiment(NamedTuple): 2025-07-17T06:33:19.9550505Z rollout_perc: float = ( 2025-07-17T06:33:19.9551100Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:33:19.9551725Z ) 2025-07-17T06:33:19.9552097Z all_branches: bool = ( 2025-07-17T06:33:19.9552682Z False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:33:19.9553312Z ) 2025-07-17T06:33:19.9553641Z default: bool = ( 2025-07-17T06:33:19.9554668Z True # If True, the experiment is enabled by default for all queries 2025-07-17T06:33:19.9555275Z ) 2025-07-17T06:33:19.9555455Z 2025-07-17T06:33:19.9555630Z # Add more fields as needed 2025-07-17T06:33:19.9555911Z 2025-07-17T06:33:19.9555925Z 2025-07-17T06:33:19.9556100Z class Settings(NamedTuple): 2025-07-17T06:33:19.9556507Z """ 2025-07-17T06:33:19.9556935Z Settings for the experiments that can be opted into. 2025-07-17T06:33:19.9557465Z """ 2025-07-17T06:33:19.9557655Z 2025-07-17T06:33:19.9557848Z experiments: dict[str, Experiment] = {} 2025-07-17T06:33:19.9558186Z 2025-07-17T06:33:19.9558192Z 2025-07-17T06:33:19.9558395Z class ColorFormatter(logging.Formatter): 2025-07-17T06:33:19.9558970Z """Color codes the log messages based on the log level""" 2025-07-17T06:33:19.9559373Z 2025-07-17T06:33:19.9559524Z COLORS = { 2025-07-17T06:33:19.9559896Z "WARNING": "\033[33m", # Yellow 2025-07-17T06:33:19.9560526Z "ERROR": "\033[31m", # Red 2025-07-17T06:33:19.9560979Z "CRITICAL": "\033[31m", # Red 2025-07-17T06:33:19.9561456Z "INFO": "\033[0m", # Reset 2025-07-17T06:33:19.9561913Z "DEBUG": "\033[0m", # Reset 2025-07-17T06:33:19.9562342Z } 2025-07-17T06:33:19.9562520Z 2025-07-17T06:33:19.9562733Z def format(self, record: LogRecord) -> str: 2025-07-17T06:33:19.9563430Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:33:19.9564448Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:33:19.9564994Z return super().format(record) 2025-07-17T06:33:19.9565313Z 2025-07-17T06:33:19.9565320Z 2025-07-17T06:33:19.9565501Z handler = logging.StreamHandler() 2025-07-17T06:33:19.9566164Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:33:19.9566672Z 2025-07-17T06:33:19.9566901Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:33:19.9567463Z log.addHandler(handler) 2025-07-17T06:33:19.9567876Z log.setLevel(logging.INFO) 2025-07-17T06:33:19.9568152Z 2025-07-17T06:33:19.9568158Z 2025-07-17T06:33:19.9568389Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:33:19.9568906Z """ 2025-07-17T06:33:19.9569375Z Defines outputs of the github action that invokes this script 2025-07-17T06:33:19.9569951Z """ 2025-07-17T06:33:19.9570284Z if not GITHUB_OUTPUT: 2025-07-17T06:33:19.9571274Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:33:19.9572292Z log.warning( 2025-07-17T06:33:19.9573069Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-07-17T06:33:19.9574129Z ) 2025-07-17T06:33:19.9585533Z print(f"::set-output name={key}::{value}") 2025-07-17T06:33:19.9586105Z return 2025-07-17T06:33:19.9586356Z 2025-07-17T06:33:19.9586769Z with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:33:19.9587374Z log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:33:19.9587901Z f.write(f"{key}={value}\n") 2025-07-17T06:33:19.9588205Z 2025-07-17T06:33:19.9588219Z 2025-07-17T06:33:19.9588509Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:33:19.9589097Z return frozenset( 2025-07-17T06:33:19.9589673Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:33:19.9590319Z ) 2025-07-17T06:33:19.9590502Z 2025-07-17T06:33:19.9590509Z 2025-07-17T06:33:19.9590683Z def parse_args() -> Any: 2025-07-17T06:33:19.9591268Z parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:33:19.9592070Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:33:19.9592797Z parser.add_argument( 2025-07-17T06:33:19.9593221Z "--github-issue-repo", 2025-07-17T06:33:19.9593892Z type=str, 2025-07-17T06:33:19.9594448Z required=False, 2025-07-17T06:33:19.9594887Z default="pytorch/test-infra", 2025-07-17T06:33:19.9595389Z help="GitHub repo to get the issue", 2025-07-17T06:33:19.9595856Z ) 2025-07-17T06:33:19.9596197Z parser.add_argument( 2025-07-17T06:33:19.9596604Z "--github-repo", 2025-07-17T06:33:19.9596998Z type=str, 2025-07-17T06:33:19.9597357Z required=True, 2025-07-17T06:33:19.9597785Z help="GitHub repo where CI is running", 2025-07-17T06:33:19.9598258Z ) 2025-07-17T06:33:19.9598596Z parser.add_argument( 2025-07-17T06:33:19.9599150Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:33:19.9599756Z ) 2025-07-17T06:33:19.9600087Z parser.add_argument( 2025-07-17T06:33:19.9600665Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:33:19.9601281Z ) 2025-07-17T06:33:19.9601777Z parser.add_argument( 2025-07-17T06:33:19.9602372Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:33:19.9603003Z ) 2025-07-17T06:33:19.9603334Z parser.add_argument( 2025-07-17T06:33:19.9604131Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:33:19.9604792Z ) 2025-07-17T06:33:19.9605122Z parser.add_argument( 2025-07-17T06:33:19.9605537Z "--github-ref-type", 2025-07-17T06:33:19.9605960Z type=str, 2025-07-17T06:33:19.9606323Z required=True, 2025-07-17T06:33:19.9606764Z help="Current GitHub ref type, branch or tag", 2025-07-17T06:33:19.9607259Z ) 2025-07-17T06:33:19.9607597Z parser.add_argument( 2025-07-17T06:33:19.9608021Z "--eligible-experiments", 2025-07-17T06:33:19.9608497Z type=_str_comma_separated_to_set, 2025-07-17T06:33:19.9608968Z required=False, 2025-07-17T06:33:19.9609352Z default="", 2025-07-17T06:33:19.9610133Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:33:19.9610982Z ) 2025-07-17T06:33:19.9611319Z parser.add_argument( 2025-07-17T06:33:19.9611738Z "--opt-out-experiments", 2025-07-17T06:33:19.9612208Z type=_str_comma_separated_to_set, 2025-07-17T06:33:19.9612687Z required=False, 2025-07-17T06:33:19.9613075Z default="", 2025-07-17T06:33:19.9613430Z help=( 2025-07-17T06:33:19.9614164Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:33:19.9615197Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:33:19.9615973Z ), 2025-07-17T06:33:19.9616306Z ) 2025-07-17T06:33:19.9616645Z parser.add_argument( 2025-07-17T06:33:19.9617050Z "--pr-number", 2025-07-17T06:33:19.9617427Z type=str, 2025-07-17T06:33:19.9617806Z required=False, 2025-07-17T06:33:19.9618178Z default="", 2025-07-17T06:33:19.9618753Z help="the optional PR number where this is run", 2025-07-17T06:33:19.9619274Z ) 2025-07-17T06:33:19.9619460Z 2025-07-17T06:33:19.9619637Z return parser.parse_args() 2025-07-17T06:33:19.9619922Z 2025-07-17T06:33:19.9619928Z 2025-07-17T06:33:19.9620307Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:33:19.9621005Z auth = Auth.Token(github_token) 2025-07-17T06:33:19.9621478Z return Github(auth=auth) 2025-07-17T06:33:19.9621746Z 2025-07-17T06:33:19.9621753Z 2025-07-17T06:33:19.9622175Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:33:19.9622906Z repo = gh.get_repo(repo) 2025-07-17T06:33:19.9623362Z return repo.get_issue(number=issue_num) 2025-07-17T06:33:19.9623819Z 2025-07-17T06:33:19.9623826Z 2025-07-17T06:33:19.9624006Z def get_potential_pr_author( 2025-07-17T06:33:19.9624619Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:33:19.9625230Z ) -> str: 2025-07-17T06:33:19.9625711Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:33:19.9626442Z # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:33:19.9627119Z # embedded in the tag name: ciflow// 2025-07-17T06:33:19.9627502Z 2025-07-17T06:33:19.9627677Z gh = get_gh_client(github_token) 2025-07-17T06:33:19.9627988Z 2025-07-17T06:33:19.9628237Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:33:19.9628805Z split_tag = ref_name.split("/") 2025-07-17T06:33:19.9629258Z if ( 2025-07-17T06:33:19.9629620Z len(split_tag) == 3 2025-07-17T06:33:19.9630060Z and split_tag[0] == "ciflow" 2025-07-17T06:33:19.9630546Z and split_tag[2].isnumeric() 2025-07-17T06:33:19.9630992Z ): 2025-07-17T06:33:19.9631485Z pr_number = split_tag[2] 2025-07-17T06:33:19.9631924Z try: 2025-07-17T06:33:19.9632330Z repository = gh.get_repo(repo) 2025-07-17T06:33:19.9632899Z pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:33:19.9633447Z except Exception as e: 2025-07-17T06:33:19.9634175Z raise Exception( # noqa: TRY002 2025-07-17T06:33:19.9634835Z f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:33:19.9635431Z ) from e 2025-07-17T06:33:19.9635926Z return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:33:19.9636569Z # In all other cases, return the original input username 2025-07-17T06:33:19.9637100Z return username 2025-07-17T06:33:19.9637327Z 2025-07-17T06:33:19.9637334Z 2025-07-17T06:33:19.9637544Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:33:19.9638035Z """ 2025-07-17T06:33:19.9638622Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:33:19.9639338Z """ 2025-07-17T06:33:19.9639841Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:33:19.9640328Z 2025-07-17T06:33:19.9640335Z 2025-07-17T06:33:19.9640520Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:33:19.9640974Z try: 2025-07-17T06:33:19.9641342Z data = yaml.safe_load(yaml_text) 2025-07-17T06:33:19.9641815Z return data 2025-07-17T06:33:19.9642197Z except yaml.YAMLError: 2025-07-17T06:33:19.9642644Z log.exception("Error loading YAML") 2025-07-17T06:33:19.9643112Z raise 2025-07-17T06:33:19.9643312Z 2025-07-17T06:33:19.9643326Z 2025-07-17T06:33:19.9644235Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:33:19.9644949Z """ 2025-07-17T06:33:19.9645527Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:33:19.9646086Z 2025-07-17T06:33:19.9646571Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:19.9647272Z and the text below is the list of opted in users. 2025-07-17T06:33:19.9647640Z 2025-07-17T06:33:19.9647994Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:33:19.9648629Z """ 2025-07-17T06:33:19.9649043Z rollout_state_parts = rollout_state.split("---") 2025-07-17T06:33:19.9649583Z if len(rollout_state_parts) >= 2: 2025-07-17T06:33:19.9650141Z return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:33:19.9650674Z else: 2025-07-17T06:33:19.9651015Z return "", rollout_state 2025-07-17T06:33:19.9651293Z 2025-07-17T06:33:19.9651299Z 2025-07-17T06:33:19.9651491Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:33:19.9651949Z """ 2025-07-17T06:33:19.9652426Z Dictionary of users with a list of features they have opted into 2025-07-17T06:33:19.9653007Z """ 2025-07-17T06:33:19.9653200Z 2025-07-17T06:33:19.9653208Z 2025-07-17T06:33:19.9653515Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:33:19.9654236Z """ 2025-07-17T06:33:19.9654884Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-07-17T06:33:19.9655510Z 2025-07-17T06:33:19.9656083Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:33:19.9656988Z - Example line: "@User1,lf,split_build" 2025-07-17T06:33:19.9657621Z - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:33:19.9658062Z 2025-07-17T06:33:19.9658069Z 2025-07-17T06:33:19.9658218Z """ 2025-07-17T06:33:19.9658561Z optins = UserOptins() 2025-07-17T06:33:19.9659017Z for user in user_optin_text.split("\n"): 2025-07-17T06:33:19.9659532Z user = user.strip("\r\n\t -") 2025-07-17T06:33:19.9660227Z if not user or not user.startswith("@"): 2025-07-17T06:33:19.9660755Z # Not a valid user. Skip 2025-07-17T06:33:19.9661213Z continue 2025-07-17T06:33:19.9661437Z 2025-07-17T06:33:19.9661584Z if user: 2025-07-17T06:33:19.9661993Z usr_name = user.split(",")[0].strip("@") 2025-07-17T06:33:19.9662633Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:33:19.9663086Z 2025-07-17T06:33:19.9663240Z return optins 2025-07-17T06:33:19.9663457Z 2025-07-17T06:33:19.9663463Z 2025-07-17T06:33:19.9663854Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:33:19.9664410Z """ 2025-07-17T06:33:19.9664779Z Check if the experiment name is valid. 2025-07-17T06:33:19.9665251Z A valid name: 2025-07-17T06:33:19.9665841Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:33:19.9666704Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:33:19.9667364Z - Cannot contain spaces 2025-07-17T06:33:19.9667791Z """ 2025-07-17T06:33:19.9667971Z 2025-07-17T06:33:19.9668213Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:33:19.9668847Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:33:19.9669251Z 2025-07-17T06:33:19.9669398Z if valid: 2025-07-17T06:33:19.9721420Z return True 2025-07-17T06:33:19.9721921Z 2025-07-17T06:33:19.9722245Z log.error( 2025-07-17T06:33:19.9724325Z 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-07-17T06:33:19.9725884Z ) 2025-07-17T06:33:19.9726221Z return False 2025-07-17T06:33:19.9726448Z 2025-07-17T06:33:19.9726455Z 2025-07-17T06:33:19.9726746Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:33:19.9727567Z """ 2025-07-17T06:33:19.9728395Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:33:19.9729080Z """ 2025-07-17T06:33:19.9729402Z try: 2025-07-17T06:33:19.9729750Z if settings_text: 2025-07-17T06:33:19.9730419Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:33:19.9731176Z # for easy reading 2025-07-17T06:33:19.9731902Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:33:19.9732748Z # the backtick character in shell commands. 2025-07-17T06:33:19.9733314Z backtick = chr(96) # backtick character 2025-07-17T06:33:19.9734180Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:33:19.9734802Z settings = load_yaml(settings_text) 2025-07-17T06:33:19.9735137Z 2025-07-17T06:33:19.9735519Z # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:33:19.9736221Z experiments = {} 2025-07-17T06:33:19.9736486Z 2025-07-17T06:33:19.9736853Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:33:19.9737545Z if not is_valid_experiment_name(exp_name): 2025-07-17T06:33:19.9738566Z # 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-07-17T06:33:19.9739513Z continue 2025-07-17T06:33:19.9739781Z 2025-07-17T06:33:19.9739952Z valid_settings = {} 2025-07-17T06:33:19.9740425Z for setting in exp_settings: 2025-07-17T06:33:19.9740955Z if setting not in Experiment._fields: 2025-07-17T06:33:19.9741465Z log.warning( 2025-07-17T06:33:19.9742111Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:33:19.9742933Z ) 2025-07-17T06:33:19.9743323Z else: 2025-07-17T06:33:19.9744009Z valid_settings[setting] = exp_settings[setting] 2025-07-17T06:33:19.9744433Z 2025-07-17T06:33:19.9744689Z experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:33:19.9745278Z return Settings(experiments) 2025-07-17T06:33:19.9745601Z 2025-07-17T06:33:19.9745769Z except Exception: 2025-07-17T06:33:19.9746205Z log.exception("Failed to parse settings") 2025-07-17T06:33:19.9746563Z 2025-07-17T06:33:19.9746729Z return Settings() 2025-07-17T06:33:19.9746959Z 2025-07-17T06:33:19.9746966Z 2025-07-17T06:33:19.9747196Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:33:19.9747711Z """ 2025-07-17T06:33:19.9748106Z Parse settings, if any, from the rollout state. 2025-07-17T06:33:19.9748478Z 2025-07-17T06:33:19.9748808Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:19.9749505Z and the text below is the list of opted in users. 2025-07-17T06:33:19.9749874Z 2025-07-17T06:33:19.9750246Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:33:19.9750916Z """ 2025-07-17T06:33:19.9751414Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:19.9752117Z return parse_settings_from_text(settings_text) 2025-07-17T06:33:19.9752478Z 2025-07-17T06:33:19.9752485Z 2025-07-17T06:33:19.9752717Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:33:19.9753224Z """ 2025-07-17T06:33:19.9753588Z Parse users from the rollout state. 2025-07-17T06:33:19.9754121Z 2025-07-17T06:33:19.9754268Z """ 2025-07-17T06:33:19.9754760Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:19.9755429Z return parse_user_opt_in_from_text(users_text) 2025-07-17T06:33:19.9755804Z 2025-07-17T06:33:19.9755811Z 2025-07-17T06:33:19.9756342Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:19.9757043Z """ 2025-07-17T06:33:19.9757429Z Check if a user is opted into an experiment 2025-07-17T06:33:19.9757917Z """ 2025-07-17T06:33:19.9758342Z return experiment_name in user_optins.get(user, []) 2025-07-17T06:33:19.9758719Z 2025-07-17T06:33:19.9758726Z 2025-07-17T06:33:19.9759118Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:19.9759790Z """ 2025-07-17T06:33:19.9760241Z Check if a user explicitly opted out of an experiment 2025-07-17T06:33:19.9760763Z """ 2025-07-17T06:33:19.9761226Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:33:19.9761861Z experiment_optout = "-" + experiment_name 2025-07-17T06:33:19.9762443Z if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:33:19.9762992Z return False 2025-07-17T06:33:19.9763221Z 2025-07-17T06:33:19.9763479Z if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:33:19.9764237Z log.warning( 2025-07-17T06:33:19.9764965Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:33:19.9765757Z ) 2025-07-17T06:33:19.9765942Z 2025-07-17T06:33:19.9766098Z return True 2025-07-17T06:33:19.9766307Z 2025-07-17T06:33:19.9766314Z 2025-07-17T06:33:19.9766474Z def get_runner_prefix( 2025-07-17T06:33:19.9766874Z rollout_state: str, 2025-07-17T06:33:19.9767289Z workflow_requestors: Iterable[str], 2025-07-17T06:33:19.9767752Z branch: str, 2025-07-17T06:33:19.9768194Z eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:19.9768800Z opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:19.9769330Z is_canary: bool = False, 2025-07-17T06:33:19.9769740Z ) -> str: 2025-07-17T06:33:19.9770274Z settings = parse_settings(rollout_state) 2025-07-17T06:33:19.9770807Z user_optins = parse_users(rollout_state) 2025-07-17T06:33:19.9771144Z 2025-07-17T06:33:19.9771309Z fleet_prefix = "" 2025-07-17T06:33:19.9771687Z prefixes = [] 2025-07-17T06:33:19.9772265Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:33:19.9773109Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:33:19.9774014Z log.info( 2025-07-17T06:33:19.9774657Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:33:19.9775359Z ) 2025-07-17T06:33:19.9775708Z continue 2025-07-17T06:33:19.9775935Z 2025-07-17T06:33:19.9776109Z if opt_out_experiments: 2025-07-17T06:33:19.9776600Z if experiment_name in opt_out_experiments: 2025-07-17T06:33:19.9777178Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:33:19.9777727Z log.info( 2025-07-17T06:33:19.9778565Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:33:19.9779451Z ) 2025-07-17T06:33:19.9779816Z continue 2025-07-17T06:33:19.9780061Z 2025-07-17T06:33:19.9780231Z if eligible_experiments: 2025-07-17T06:33:19.9780746Z if experiment_name not in eligible_experiments: 2025-07-17T06:33:19.9781320Z exp_list = ", ".join(eligible_experiments) 2025-07-17T06:33:19.9781832Z log.info( 2025-07-17T06:33:19.9782542Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:33:19.9783299Z ) 2025-07-17T06:33:19.9784325Z continue 2025-07-17T06:33:19.9784812Z elif not experiment_settings.default: 2025-07-17T06:33:19.9785310Z log.info( 2025-07-17T06:33:19.9786056Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:33:19.9786734Z ) 2025-07-17T06:33:19.9787074Z continue 2025-07-17T06:33:19.9787307Z 2025-07-17T06:33:19.9787566Z # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:33:19.9788125Z opted_out_users = [ 2025-07-17T06:33:19.9788534Z requestor 2025-07-17T06:33:19.9788950Z for requestor in workflow_requestors 2025-07-17T06:33:19.9789564Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:33:19.9790137Z ] 2025-07-17T06:33:19.9790324Z 2025-07-17T06:33:19.9790489Z if opted_out_users: 2025-07-17T06:33:19.9790899Z log.info( 2025-07-17T06:33:19.9791506Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:33:19.9792144Z ) 2025-07-17T06:33:19.9792487Z continue 2025-07-17T06:33:19.9792725Z 2025-07-17T06:33:19.9792974Z # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:33:19.9793533Z opted_in_users = [ 2025-07-17T06:33:19.9794039Z requestor 2025-07-17T06:33:19.9794467Z for requestor in workflow_requestors 2025-07-17T06:33:19.9795070Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:33:19.9795634Z ] 2025-07-17T06:33:19.9795822Z 2025-07-17T06:33:19.9795984Z enabled = False 2025-07-17T06:33:19.9796384Z if opted_in_users: 2025-07-17T06:33:19.9796778Z log.info( 2025-07-17T06:33:19.9797329Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:33:19.9797943Z ) 2025-07-17T06:33:19.9798296Z enabled = True 2025-07-17T06:33:19.9798547Z 2025-07-17T06:33:19.9798757Z elif experiment_settings.rollout_perc: 2025-07-17T06:33:19.9799512Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:33:19.9800564Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:33:19.9801156Z log.info( 2025-07-17T06:33:19.9801940Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:33:19.9802767Z ) 2025-07-17T06:33:19.9803136Z enabled = True 2025-07-17T06:33:19.9803406Z 2025-07-17T06:33:19.9803559Z if enabled: 2025-07-17T06:33:19.9804216Z label = experiment_name 2025-07-17T06:33:19.9804793Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:33:19.9805543Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:33:19.9806349Z # - If it's enabled, then we always list it's prefix first 2025-07-17T06:33:19.9807055Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:33:19.9807660Z if is_canary: 2025-07-17T06:33:19.9808114Z label += CANARY_FLEET_SUFFIX 2025-07-17T06:33:19.9808609Z fleet_prefix = label 2025-07-17T06:33:19.9809059Z else: 2025-07-17T06:33:19.9809445Z prefixes.append(label) 2025-07-17T06:33:19.9809766Z 2025-07-17T06:33:19.9809929Z if len(prefixes) > 1: 2025-07-17T06:33:19.9810325Z log.error( 2025-07-17T06:33:19.9811259Z 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-07-17T06:33:19.9812259Z ) 2025-07-17T06:33:19.9812610Z prefixes = prefixes[:1] 2025-07-17T06:33:19.9812891Z 2025-07-17T06:33:19.9813077Z # Fleet always comes first 2025-07-17T06:33:19.9813503Z if fleet_prefix: 2025-07-17T06:33:19.9814096Z prefixes.insert(0, fleet_prefix) 2025-07-17T06:33:19.9814436Z 2025-07-17T06:33:19.9814854Z return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:33:19.9815253Z 2025-07-17T06:33:19.9815260Z 2025-07-17T06:33:19.9815662Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:33:19.9816358Z """ 2025-07-17T06:33:19.9816891Z Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:33:19.9817408Z 2025-07-17T06:33:19.9817778Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:33:19.9818417Z """ 2025-07-17T06:33:19.9818769Z gh = get_gh_client(github_token) 2025-07-17T06:33:19.9819255Z issue = get_issue(gh, repo, issue_num) 2025-07-17T06:33:19.9819835Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:33:19.9820232Z 2025-07-17T06:33:19.9820239Z 2025-07-17T06:33:19.9820613Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:33:19.9821303Z for _ in range(num_retries): 2025-07-17T06:33:19.9821734Z try: 2025-07-17T06:33:19.9822124Z req = Request(url=url, headers=headers) 2025-07-17T06:33:19.9822731Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:33:19.9823306Z return json.loads(content) 2025-07-17T06:33:19.9823926Z except Exception as e: 2025-07-17T06:33:19.9824429Z log.warning(f"Could not download {url}: {e}") 2025-07-17T06:33:19.9824795Z 2025-07-17T06:33:19.9825141Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:33:19.9825788Z return {} 2025-07-17T06:33:19.9825988Z 2025-07-17T06:33:19.9825995Z 2025-07-17T06:33:19.9826136Z @cache 2025-07-17T06:33:19.9826705Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:33:19.9827376Z """ 2025-07-17T06:33:19.9827739Z Dynamically get PR information 2025-07-17T06:33:19.9828312Z """ 2025-07-17T06:33:19.9828768Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:33:19.9829331Z headers = { 2025-07-17T06:33:19.9829741Z "Accept": "application/vnd.github.v3+json", 2025-07-17T06:33:19.9830298Z "Authorization": f"token {github_token}", 2025-07-17T06:33:19.9830780Z } 2025-07-17T06:33:19.9831172Z json_response: dict[str, Any] = download_json( 2025-07-17T06:33:19.9831718Z url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:33:19.9832219Z headers=headers, 2025-07-17T06:33:19.9832604Z ) 2025-07-17T06:33:19.9832792Z 2025-07-17T06:33:19.9832955Z if not json_response: 2025-07-17T06:33:19.9833473Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:33:19.9834141Z return {} 2025-07-17T06:33:19.9834358Z 2025-07-17T06:33:19.9834545Z return json_response 2025-07-17T06:33:19.9834797Z 2025-07-17T06:33:19.9834803Z 2025-07-17T06:33:19.9835167Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:33:19.9835842Z """ 2025-07-17T06:33:19.9836325Z Dynamically get the latest list of labels from the pull request 2025-07-17T06:33:19.9836903Z """ 2025-07-17T06:33:19.9837348Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:33:19.9837892Z return { 2025-07-17T06:33:19.9838418Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:33:19.9839037Z } 2025-07-17T06:33:19.9839224Z 2025-07-17T06:33:19.9839230Z 2025-07-17T06:33:19.9839389Z def main() -> None: 2025-07-17T06:33:19.9839766Z args = parse_args() 2025-07-17T06:33:19.9840016Z 2025-07-17T06:33:19.9840214Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:33:19.9840561Z 2025-07-17T06:33:19.9840744Z # Check if the PR is opt-out 2025-07-17T06:33:19.9841180Z if args.pr_number: 2025-07-17T06:33:19.9841771Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:33:19.9842581Z if OPT_OUT_LABEL in labels: 2025-07-17T06:33:19.9843039Z log.info( 2025-07-17T06:33:19.9843652Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:33:19.9844448Z ) 2025-07-17T06:33:19.9844953Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:19.9845556Z sys.exit() 2025-07-17T06:33:19.9845790Z 2025-07-17T06:33:19.9845939Z try: 2025-07-17T06:33:19.9846321Z rollout_state = get_rollout_state_from_issue( 2025-07-17T06:33:19.9846966Z args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:33:19.9847531Z ) 2025-07-17T06:33:19.9847728Z 2025-07-17T06:33:19.9847916Z username = get_potential_pr_author( 2025-07-17T06:33:19.9848404Z args.github_token, 2025-07-17T06:33:19.9848839Z args.github_repo, 2025-07-17T06:33:19.9849270Z args.github_actor, 2025-07-17T06:33:19.9849707Z args.github_ref_type, 2025-07-17T06:33:19.9850156Z args.github_branch, 2025-07-17T06:33:19.9850562Z ) 2025-07-17T06:33:19.9850756Z 2025-07-17T06:33:19.9851021Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:33:19.9851421Z 2025-07-17T06:33:19.9851622Z runner_label_prefix = get_runner_prefix( 2025-07-17T06:33:19.9852123Z rollout_state, 2025-07-17T06:33:19.9852559Z (args.github_issue_owner, username), 2025-07-17T06:33:19.9853055Z args.github_branch, 2025-07-17T06:33:19.9853504Z args.eligible_experiments, 2025-07-17T06:33:19.9854292Z args.opt_out_experiments, 2025-07-17T06:33:19.9854761Z is_canary, 2025-07-17T06:33:19.9855129Z ) 2025-07-17T06:33:19.9855326Z 2025-07-17T06:33:19.9855499Z except Exception as e: 2025-07-17T06:33:19.9855899Z log.error( 2025-07-17T06:33:19.9856510Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:33:19.9857395Z ) 2025-07-17T06:33:19.9857591Z 2025-07-17T06:33:19.9857892Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:19.9858333Z 2025-07-17T06:33:19.9858341Z 2025-07-17T06:33:19.9858505Z if __name__ == "__main__": 2025-07-17T06:33:19.9858898Z main() 2025-07-17T06:33:19.9859090Z 2025-07-17T06:33:19.9953978Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:33:19.9954872Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:33:19.9983370Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:19.9983962Z env: 2025-07-17T06:33:19.9984580Z GITHUB_TOKEN: *** 2025-07-17T06:33:19.9984975Z ISSUE_NUMBER: 5132 2025-07-17T06:33:19.9985387Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:19.9985863Z ISSUE_OWNER: 2025-07-17T06:33:19.9986239Z CHECK_EXPERIMENTS: 2025-07-17T06:33:19.9986629Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:19.9987037Z PR_NUMBER: 2025-07-17T06:33:19.9987382Z ##[endgroup] 2025-07-17T06:33:20.3766111Z Defaulting to user installation because normal site-packages is not writeable 2025-07-17T06:33:20.7362959Z Collecting urllib3==1.26.18 2025-07-17T06:33:20.7728071Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-07-17T06:33:20.7970958Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.9 MB/s eta 0:00:00 2025-07-17T06:33:20.8161343Z Collecting PyGithub==2.3.0 2025-07-17T06:33:20.8201609Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-07-17T06:33:20.8677894Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-07-17T06:33:20.8714339Z 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-07-17T06:33:20.8762066Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-07-17T06:33:20.8782171Z 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-07-17T06:33:20.8798178Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-07-17T06:33:20.9057069Z Collecting Deprecated (from PyGithub==2.3.0) 2025-07-17T06:33:20.9093025Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-07-17T06:33:20.9334315Z 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-07-17T06:33:21.0444926Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:33:21.0516674Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-07-17T06:33:21.1579429Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-07-17T06:33:21.1656913Z 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-07-17T06:33:21.1836337Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:33:21.1894162Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-07-17T06:33:21.2137755Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-07-17T06:33:21.2200137Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 31.6 MB/s eta 0:00:00 2025-07-17T06:33:21.2244292Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-07-17T06:33:21.2306126Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 82.8 MB/s eta 0:00:00 2025-07-17T06:33:21.2341846Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-07-17T06:33:21.2431449Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 123.1 MB/s eta 0:00:00 2025-07-17T06:33:21.2463513Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-07-17T06:33:21.2518347Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-07-17T06:33:21.2587994Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 94.5 MB/s eta 0:00:00 2025-07-17T06:33:21.2627417Z 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-07-17T06:33:21.2669384Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 31.7 MB/s eta 0:00:00 2025-07-17T06:33:21.2702856Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-07-17T06:33:21.2747411Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 39.6 MB/s eta 0:00:00 2025-07-17T06:33:21.5849081Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-07-17T06:33:22.1478725Z 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-07-17T06:33:22.2540886Z ##[group]Run curr_branch="main" 2025-07-17T06:33:22.2541283Z curr_branch="main" 2025-07-17T06:33:22.2541553Z curr_ref_type="branch" 2025-07-17T06:33:22.2541842Z echo "Current branch is '$curr_branch'" 2025-07-17T06:33:22.2542150Z  2025-07-17T06:33:22.2542375Z python3 runner_determinator.py \ 2025-07-17T06:33:22.2542704Z  --github-token "$GITHUB_TOKEN" \ 2025-07-17T06:33:22.2543029Z  --github-issue "$ISSUE_NUMBER" \ 2025-07-17T06:33:22.2543328Z  --github-branch "$curr_branch" \ 2025-07-17T06:33:22.2543639Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-07-17T06:33:22.2544126Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-07-17T06:33:22.2544456Z  --github-ref-type "$curr_ref_type" \ 2025-07-17T06:33:22.2544792Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-07-17T06:33:22.2545154Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-07-17T06:33:22.2545591Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-07-17T06:33:22.2545932Z  --pr-number "${PR_NUMBER}" 2025-07-17T06:33:22.2575458Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:22.2575732Z env: 2025-07-17T06:33:22.2576313Z GITHUB_TOKEN: *** 2025-07-17T06:33:22.2576550Z ISSUE_NUMBER: 5132 2025-07-17T06:33:22.2576803Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:22.2577066Z ISSUE_OWNER: 2025-07-17T06:33:22.2577292Z CHECK_EXPERIMENTS: 2025-07-17T06:33:22.2577523Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:22.2577765Z PR_NUMBER: 2025-07-17T06:33:22.2577980Z ##[endgroup] 2025-07-17T06:33:22.2627790Z Current branch is 'main' 2025-07-17T06:33:24.1324037Z INFO : Based on rollout percentage of 25%, enabling experiment lf. 2025-07-17T06:33:24.1325536Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-07-17T06:33:24.1327419Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-07-17T06:33:24.1328025Z INFO : Setting output: label-type='lf.' 2025-07-17T06:33:24.1702209Z Evaluate and set job outputs 2025-07-17T06:33:24.1709379Z Set output 'label-type' 2025-07-17T06:33:24.1711282Z Cleaning up orphan processes