2025-07-17T06:38:20.2362891Z Current runner version: '2.326.0' 2025-07-17T06:38:20.2388683Z ##[group]Runner Image Provisioner 2025-07-17T06:38:20.2389534Z Hosted Compute Agent 2025-07-17T06:38:20.2390157Z Version: 20250711.363 2025-07-17T06:38:20.2390734Z Commit: 6785254374ce925a23743850c1cb91912ce5c14c 2025-07-17T06:38:20.2391465Z Build Date: 2025-07-11T20:04:25Z 2025-07-17T06:38:20.2392009Z ##[endgroup] 2025-07-17T06:38:20.2392611Z ##[group]Operating System 2025-07-17T06:38:20.2393147Z Ubuntu 2025-07-17T06:38:20.2393619Z 24.04.2 2025-07-17T06:38:20.2394160Z LTS 2025-07-17T06:38:20.2394796Z ##[endgroup] 2025-07-17T06:38:20.2395296Z ##[group]Runner Image 2025-07-17T06:38:20.2395888Z Image: ubuntu-24.04 2025-07-17T06:38:20.2396395Z Version: 20250710.1.0 2025-07-17T06:38:20.2397368Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250710.1/images/ubuntu/Ubuntu2404-Readme.md 2025-07-17T06:38:20.2398987Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250710.1 2025-07-17T06:38:20.2400126Z ##[endgroup] 2025-07-17T06:38:20.2401193Z ##[group]GITHUB_TOKEN Permissions 2025-07-17T06:38:20.2403004Z Contents: read 2025-07-17T06:38:20.2403584Z Metadata: read 2025-07-17T06:38:20.2404143Z ##[endgroup] 2025-07-17T06:38:20.2406478Z Secret source: Actions 2025-07-17T06:38:20.2407277Z Prepare workflow directory 2025-07-17T06:38:20.2940630Z Prepare all required actions 2025-07-17T06:38:20.2997381Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (a38f433be2e94a64b095a44ba39879d02d0c2316) 2025-07-17T06:38:20.3002434Z ##[group] Inputs 2025-07-17T06:38:20.3003112Z check_experiments: 2025-07-17T06:38:20.3003682Z opt_out_experiments: 2025-07-17T06:38:20.3004298Z triggering_actor: pytorchmergebot 2025-07-17T06:38:20.3005309Z issue_owner: 2025-07-17T06:38:20.3005784Z curr_branch: main 2025-07-17T06:38:20.3006422Z curr_ref_type: branch 2025-07-17T06:38:20.3007095Z issue_number: 5132 2025-07-17T06:38:20.3007602Z ##[endgroup] 2025-07-17T06:38:20.3008382Z Complete job name: before-test / get-label-type / runner-determinator 2025-07-17T06:38:20.8730949Z ##[group]Run cat < runner_determinator.py 2025-07-17T06:38:20.8733523Z cat < runner_determinator.py 2025-07-17T06:38:20.8734206Z # flake8: noqa: G004 2025-07-17T06:38:20.8735321Z  2025-07-17T06:38:20.8736147Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:38:20.8737291Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:38:20.8738415Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:38:20.8739168Z  2025-07-17T06:38:20.8739639Z """ 2025-07-17T06:38:20.8740443Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:38:20.8741545Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:38:20.8742829Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:38:20.8743910Z of which runners should be used to run which job. 2025-07-17T06:38:20.8744917Z  2025-07-17T06:38:20.8745823Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:38:20.8746989Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:38:20.8748036Z settings are considered to be empty with only the second part, the user 2025-07-17T06:38:20.8748991Z list, defined. 2025-07-17T06:38:20.8749560Z  2025-07-17T06:38:20.8750249Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:38:20.8751404Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:38:20.8752429Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:38:20.8753200Z  2025-07-17T06:38:20.8754351Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:38:20.8755731Z The user list is also a comma separated list of additional features or 2025-07-17T06:38:20.8756682Z experiments which the user could be opted in to. 2025-07-17T06:38:20.8757344Z  2025-07-17T06:38:20.8757952Z The user list has the following rules: 2025-07-17T06:38:20.8758578Z  2025-07-17T06:38:20.8759308Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:38:20.8760457Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:38:20.8761382Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:38:20.8762077Z  2025-07-17T06:38:20.8762599Z Example config: 2025-07-17T06:38:20.8763250Z  # A list of experiments that can be opted into. 2025-07-17T06:38:20.8764115Z  # This defines the behavior they'll induce when opted into. 2025-07-17T06:38:20.8765104Z  # Expected syntax is: 2025-07-17T06:38:20.8765966Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:38:20.8767101Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:38:20.8768080Z  2025-07-17T06:38:20.8768536Z  experiments: 2025-07-17T06:38:20.8769122Z  lf: 2025-07-17T06:38:20.8769710Z  rollout_percent: 25 2025-07-17T06:38:20.8770323Z  all_branches: false 2025-07-17T06:38:20.8770953Z  default: true 2025-07-17T06:38:20.8771527Z  --- 2025-07-17T06:38:20.8772053Z  2025-07-17T06:38:20.8772539Z  # Opt-ins: 2025-07-17T06:38:20.8860447Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:38:20.8862899Z  # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:38:20.8864835Z  # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:38:20.8866336Z  # Experiments should be from the above list. 2025-07-17T06:38:20.8867350Z  2025-07-17T06:38:20.8867783Z  @User1,-lf,split_build 2025-07-17T06:38:20.8868313Z  @User2,lf 2025-07-17T06:38:20.8868788Z  @User3,split_build 2025-07-17T06:38:20.8869283Z """ 2025-07-17T06:38:20.8869675Z  2025-07-17T06:38:20.8870074Z import json 2025-07-17T06:38:20.8870526Z import logging 2025-07-17T06:38:20.8870979Z import os 2025-07-17T06:38:20.8871419Z import random 2025-07-17T06:38:20.8871866Z import re 2025-07-17T06:38:20.8872289Z import sys 2025-07-17T06:38:20.8872772Z from argparse import ArgumentParser 2025-07-17T06:38:20.8873468Z from collections.abc import Iterable 2025-07-17T06:38:20.8874085Z from functools import cache 2025-07-17T06:38:20.8874872Z from logging import LogRecord 2025-07-17T06:38:20.8875477Z from typing import Any, NamedTuple 2025-07-17T06:38:20.8876116Z from urllib.request import Request, urlopen 2025-07-17T06:38:20.8876724Z  2025-07-17T06:38:20.8877122Z import yaml 2025-07-17T06:38:20.8877600Z from github import Auth, Github 2025-07-17T06:38:20.8878179Z from github.Issue import Issue 2025-07-17T06:38:20.8878718Z  2025-07-17T06:38:20.8879100Z  2025-07-17T06:38:20.8879574Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:38:20.8880365Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:38:20.8881367Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:38:20.8882171Z  2025-07-17T06:38:20.8882884Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:38:20.8883541Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:38:20.8884152Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:38:20.8885024Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:38:20.8885612Z  2025-07-17T06:38:20.8886052Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:38:20.8886608Z  2025-07-17T06:38:20.8887036Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:38:20.8887583Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:38:20.8888086Z  2025-07-17T06:38:20.8888463Z  2025-07-17T06:38:20.8888890Z class Experiment(NamedTuple): 2025-07-17T06:38:20.8889463Z  rollout_perc: float = ( 2025-07-17T06:38:20.8890238Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:38:20.8890987Z  ) 2025-07-17T06:38:20.8891424Z  all_branches: bool = ( 2025-07-17T06:38:20.8892176Z  False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:38:20.8892920Z  ) 2025-07-17T06:38:20.8893350Z  default: bool = ( 2025-07-17T06:38:20.8894020Z  True # If True, the experiment is enabled by default for all queries 2025-07-17T06:38:20.8894912Z  ) 2025-07-17T06:38:20.8895308Z  2025-07-17T06:38:20.8895724Z  # Add more fields as needed 2025-07-17T06:38:20.8896255Z  2025-07-17T06:38:20.8896631Z  2025-07-17T06:38:20.8897043Z class Settings(NamedTuple): 2025-07-17T06:38:20.8897578Z  """ 2025-07-17T06:38:20.8898124Z  Settings for the experiments that can be opted into. 2025-07-17T06:38:20.8898767Z  """ 2025-07-17T06:38:20.8899171Z  2025-07-17T06:38:20.8899629Z  experiments: dict[str, Experiment] = {} 2025-07-17T06:38:20.8900224Z  2025-07-17T06:38:20.8900738Z  2025-07-17T06:38:20.8901214Z class ColorFormatter(logging.Formatter): 2025-07-17T06:38:20.8901931Z  """Color codes the log messages based on the log level""" 2025-07-17T06:38:20.8902588Z  2025-07-17T06:38:20.8902980Z  COLORS = { 2025-07-17T06:38:20.8903475Z  "WARNING": "\033[33m", # Yellow 2025-07-17T06:38:20.8904058Z  "ERROR": "\033[31m", # Red 2025-07-17T06:38:20.8904872Z  "CRITICAL": "\033[31m", # Red 2025-07-17T06:38:20.8905477Z  "INFO": "\033[0m", # Reset 2025-07-17T06:38:20.8906067Z  "DEBUG": "\033[0m", # Reset 2025-07-17T06:38:20.8906607Z  } 2025-07-17T06:38:20.8907008Z  2025-07-17T06:38:20.8907476Z  def format(self, record: LogRecord) -> str: 2025-07-17T06:38:20.8908325Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:38:20.8909199Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:38:20.8909883Z  return super().format(record) 2025-07-17T06:38:20.8910428Z  2025-07-17T06:38:20.8910804Z  2025-07-17T06:38:20.8911232Z handler = logging.StreamHandler() 2025-07-17T06:38:20.8912067Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:38:20.8912864Z  2025-07-17T06:38:20.8913373Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:38:20.8914042Z log.addHandler(handler) 2025-07-17T06:38:20.8914988Z log.setLevel(logging.INFO) 2025-07-17T06:38:20.8915518Z  2025-07-17T06:38:20.8915893Z  2025-07-17T06:38:20.8916408Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:38:20.8917056Z  """ 2025-07-17T06:38:20.8917645Z  Defines outputs of the github action that invokes this script 2025-07-17T06:38:20.8918518Z  """ 2025-07-17T06:38:20.8918950Z  if not GITHUB_OUTPUT: 2025-07-17T06:38:20.8920160Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:38:20.8921424Z  log.warning( 2025-07-17T06:38:20.8922402Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-07-17T06:38:20.8923436Z  ) 2025-07-17T06:38:20.8923951Z  print(f"::set-output name={key}::{value}") 2025-07-17T06:38:20.8924742Z  return 2025-07-17T06:38:20.8925195Z  2025-07-17T06:38:20.8925640Z  with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:38:20.8926300Z  log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:38:20.8926962Z  f.write(f"{key}={value}\n") 2025-07-17T06:38:20.8927515Z  2025-07-17T06:38:20.8927898Z  2025-07-17T06:38:20.8928472Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:38:20.8929207Z  return frozenset( 2025-07-17T06:38:20.8929931Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:38:20.8930687Z  ) 2025-07-17T06:38:20.8931099Z  2025-07-17T06:38:20.8931472Z  2025-07-17T06:38:20.8931880Z def parse_args() -> Any: 2025-07-17T06:38:20.8932571Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:38:20.8933550Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:38:20.8934408Z  parser.add_argument( 2025-07-17T06:38:20.8935153Z  "--github-issue-repo", 2025-07-17T06:38:20.8935709Z  type=str, 2025-07-17T06:38:20.8936202Z  required=False, 2025-07-17T06:38:20.8936882Z  default="pytorch/test-infra", 2025-07-17T06:38:20.8937547Z  help="GitHub repo to get the issue", 2025-07-17T06:38:20.8938121Z  ) 2025-07-17T06:38:20.8938559Z  parser.add_argument( 2025-07-17T06:38:20.8939087Z  "--github-repo", 2025-07-17T06:38:20.8939606Z  type=str, 2025-07-17T06:38:20.8940090Z  required=True, 2025-07-17T06:38:20.8940656Z  help="GitHub repo where CI is running", 2025-07-17T06:38:20.8941234Z  ) 2025-07-17T06:38:20.8941673Z  parser.add_argument( 2025-07-17T06:38:20.8942396Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:38:20.8943134Z  ) 2025-07-17T06:38:20.8943572Z  parser.add_argument( 2025-07-17T06:38:20.8944312Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:38:20.8945320Z  ) 2025-07-17T06:38:20.8945757Z  parser.add_argument( 2025-07-17T06:38:20.8946516Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:38:20.8947297Z  ) 2025-07-17T06:38:20.8947731Z  parser.add_argument( 2025-07-17T06:38:20.8948520Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:38:20.8949308Z  ) 2025-07-17T06:38:20.8949758Z  parser.add_argument( 2025-07-17T06:38:20.8950301Z  "--github-ref-type", 2025-07-17T06:38:20.8950844Z  type=str, 2025-07-17T06:38:20.8951328Z  required=True, 2025-07-17T06:38:20.8951936Z  help="Current GitHub ref type, branch or tag", 2025-07-17T06:38:20.8952552Z  ) 2025-07-17T06:38:20.8952986Z  parser.add_argument( 2025-07-17T06:38:20.8953685Z  "--eligible-experiments", 2025-07-17T06:38:20.8954308Z  type=_str_comma_separated_to_set, 2025-07-17T06:38:20.8955102Z  required=False, 2025-07-17T06:38:20.8955618Z  default="", 2025-07-17T06:38:20.8956601Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:38:20.8957633Z  ) 2025-07-17T06:38:20.8958089Z  parser.add_argument( 2025-07-17T06:38:20.8958638Z  "--opt-out-experiments", 2025-07-17T06:38:20.8959242Z  type=_str_comma_separated_to_set, 2025-07-17T06:38:20.8959826Z  required=False, 2025-07-17T06:38:20.8960336Z  default="", 2025-07-17T06:38:20.8960948Z  help=( 2025-07-17T06:38:20.8961744Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:38:20.8963000Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:38:20.8963921Z  ), 2025-07-17T06:38:20.8964342Z  ) 2025-07-17T06:38:20.8964946Z  parser.add_argument( 2025-07-17T06:38:20.8965482Z  "--pr-number", 2025-07-17T06:38:20.8965983Z  type=str, 2025-07-17T06:38:20.8966471Z  required=False, 2025-07-17T06:38:20.8966997Z  default="", 2025-07-17T06:38:20.8967577Z  help="the optional PR number where this is run", 2025-07-17T06:38:20.8968205Z  ) 2025-07-17T06:38:20.8968597Z  2025-07-17T06:38:20.8969018Z  return parser.parse_args() 2025-07-17T06:38:20.8969564Z  2025-07-17T06:38:20.8969942Z  2025-07-17T06:38:20.8970612Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:38:20.8971625Z  auth = Auth.Token(github_token) 2025-07-17T06:38:20.8972251Z  return Github(auth=auth) 2025-07-17T06:38:20.8972778Z  2025-07-17T06:38:20.8973156Z  2025-07-17T06:38:20.8973884Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:38:20.8974964Z  repo = gh.get_repo(repo) 2025-07-17T06:38:20.8975579Z  return repo.get_issue(number=issue_num) 2025-07-17T06:38:20.8976163Z  2025-07-17T06:38:20.8976539Z  2025-07-17T06:38:20.8976942Z def get_potential_pr_author( 2025-07-17T06:38:20.8977704Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:38:20.8978455Z ) -> str: 2025-07-17T06:38:20.8979074Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:38:20.8979998Z  # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:38:20.8980862Z  # embedded in the tag name: ciflow// 2025-07-17T06:38:20.8981507Z  2025-07-17T06:38:20.8981943Z  gh = get_gh_client(github_token) 2025-07-17T06:38:20.8982498Z  2025-07-17T06:38:20.8983016Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:38:20.8983734Z  split_tag = ref_name.split("/") 2025-07-17T06:38:20.8984293Z  if ( 2025-07-17T06:38:20.8984990Z  len(split_tag) == 3 2025-07-17T06:38:20.8985595Z  and split_tag[0] == "ciflow" 2025-07-17T06:38:20.8986218Z  and split_tag[2].isnumeric() 2025-07-17T06:38:20.8986779Z  ): 2025-07-17T06:38:20.8987243Z  pr_number = split_tag[2] 2025-07-17T06:38:20.8987804Z  try: 2025-07-17T06:38:20.8988327Z  repository = gh.get_repo(repo) 2025-07-17T06:38:20.8989193Z  pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:38:20.8989883Z  except Exception as e: 2025-07-17T06:38:20.8990490Z  raise Exception( # noqa: TRY002 2025-07-17T06:38:20.8991249Z  f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:38:20.8991967Z  ) from e 2025-07-17T06:38:20.8992614Z  return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:38:20.8993403Z  # In all other cases, return the original input username 2025-07-17T06:38:20.8994069Z  return username 2025-07-17T06:38:20.8995241Z  2025-07-17T06:38:20.8995670Z  2025-07-17T06:38:20.8996148Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:38:20.8996753Z  """ 2025-07-17T06:38:20.8997511Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:38:20.8998379Z  """ 2025-07-17T06:38:20.8999008Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:38:20.8999746Z  2025-07-17T06:38:20.9000122Z  2025-07-17T06:38:20.9000557Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:38:20.9001116Z  try: 2025-07-17T06:38:20.9001576Z  data = yaml.safe_load(yaml_text) 2025-07-17T06:38:20.9002153Z  return data 2025-07-17T06:38:20.9002659Z  except yaml.YAMLError: 2025-07-17T06:38:20.9003242Z  log.exception("Error loading YAML") 2025-07-17T06:38:20.9003818Z  raise 2025-07-17T06:38:20.9004251Z  2025-07-17T06:38:20.9004792Z  2025-07-17T06:38:20.9005488Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:38:20.9006300Z  """ 2025-07-17T06:38:20.9007166Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:38:20.9007996Z  2025-07-17T06:38:20.9008596Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:38:20.9009476Z  and the text below is the list of opted in users. 2025-07-17T06:38:20.9010130Z  2025-07-17T06:38:20.9010757Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:38:20.9011523Z  """ 2025-07-17T06:38:20.9012050Z  rollout_state_parts = rollout_state.split("---") 2025-07-17T06:38:20.9012722Z  if len(rollout_state_parts) >= 2: 2025-07-17T06:38:20.9013421Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:38:20.9014076Z  else: 2025-07-17T06:38:20.9014873Z  return "", rollout_state 2025-07-17T06:38:20.9015518Z  2025-07-17T06:38:20.9015890Z  2025-07-17T06:38:20.9016339Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:38:20.9016919Z  """ 2025-07-17T06:38:20.9017521Z  Dictionary of users with a list of features they have opted into 2025-07-17T06:38:20.9018237Z  """ 2025-07-17T06:38:20.9018632Z  2025-07-17T06:38:20.9019009Z  2025-07-17T06:38:20.9019601Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:38:20.9020335Z  """ 2025-07-17T06:38:20.9021133Z  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:38:20.9022041Z  2025-07-17T06:38:20.9022915Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:38:20.9024011Z  - Example line: "@User1,lf,split_build" 2025-07-17T06:38:20.9025185Z  - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:38:20.9025896Z  2025-07-17T06:38:20.9026265Z  2025-07-17T06:38:20.9026627Z  """ 2025-07-17T06:38:20.9027057Z  optins = UserOptins() 2025-07-17T06:38:20.9027635Z  for user in user_optin_text.split("\n"): 2025-07-17T06:38:20.9028267Z  user = user.strip("\r\n\t -") 2025-07-17T06:38:20.9028889Z  if not user or not user.startswith("@"): 2025-07-17T06:38:20.9029507Z  # Not a valid user. Skip 2025-07-17T06:38:20.9030061Z  continue 2025-07-17T06:38:20.9030525Z  2025-07-17T06:38:20.9030907Z  if user: 2025-07-17T06:38:20.9031471Z  usr_name = user.split(",")[0].strip("@") 2025-07-17T06:38:20.9032250Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:38:20.9032958Z  2025-07-17T06:38:20.9033353Z  return optins 2025-07-17T06:38:20.9033817Z  2025-07-17T06:38:20.9034174Z  2025-07-17T06:38:20.9034913Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:38:20.9035597Z  """ 2025-07-17T06:38:20.9036084Z  Check if the experiment name is valid. 2025-07-17T06:38:20.9036659Z  A valid name: 2025-07-17T06:38:20.9037403Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:38:20.9038468Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:38:20.9039254Z  - Cannot contain spaces 2025-07-17T06:38:20.9039786Z  """ 2025-07-17T06:38:20.9040174Z  2025-07-17T06:38:20.9040687Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:38:20.9041496Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:38:20.9042306Z  2025-07-17T06:38:20.9042689Z  if valid: 2025-07-17T06:38:20.9043145Z  return True 2025-07-17T06:38:20.9043617Z  2025-07-17T06:38:20.9044006Z  log.error( 2025-07-17T06:38:20.9045793Z  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:38:20.9047501Z  ) 2025-07-17T06:38:20.9047916Z  return False 2025-07-17T06:38:20.9048370Z  2025-07-17T06:38:20.9048742Z  2025-07-17T06:38:20.9049305Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:38:20.9050005Z  """ 2025-07-17T06:38:20.9050677Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:38:20.9051466Z  """ 2025-07-17T06:38:20.9051871Z  try: 2025-07-17T06:38:20.9052293Z  if settings_text: 2025-07-17T06:38:20.9053133Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:38:20.9054002Z  # for easy reading 2025-07-17T06:38:20.9055055Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:38:20.9056039Z  # the backtick character in shell commands. 2025-07-17T06:38:20.9056720Z  backtick = chr(96) # backtick character 2025-07-17T06:38:20.9057479Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:38:20.9058234Z  settings = load_yaml(settings_text) 2025-07-17T06:38:20.9058797Z  2025-07-17T06:38:20.9059465Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:38:20.9060429Z  experiments = {} 2025-07-17T06:38:20.9060943Z  2025-07-17T06:38:20.9061553Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:38:20.9062402Z  if not is_valid_experiment_name(exp_name): 2025-07-17T06:38:20.9063630Z  # 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:38:20.9065029Z  continue 2025-07-17T06:38:20.9065550Z  2025-07-17T06:38:20.9065963Z  valid_settings = {} 2025-07-17T06:38:20.9066562Z  for setting in exp_settings: 2025-07-17T06:38:20.9067200Z  if setting not in Experiment._fields: 2025-07-17T06:38:20.9067841Z  log.warning( 2025-07-17T06:38:20.9068660Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:38:20.9069440Z  ) 2025-07-17T06:38:20.9069940Z  else: 2025-07-17T06:38:20.9070543Z  valid_settings[setting] = exp_settings[setting] 2025-07-17T06:38:20.9071172Z  2025-07-17T06:38:20.9071697Z  experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:38:20.9072415Z  return Settings(experiments) 2025-07-17T06:38:20.9072967Z  2025-07-17T06:38:20.9073361Z  except Exception: 2025-07-17T06:38:20.9073940Z  log.exception("Failed to parse settings") 2025-07-17T06:38:20.9074721Z  2025-07-17T06:38:20.9075188Z  return Settings() 2025-07-17T06:38:20.9075684Z  2025-07-17T06:38:20.9076054Z  2025-07-17T06:38:20.9076750Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:38:20.9077412Z  """ 2025-07-17T06:38:20.9077926Z  Parse settings, if any, from the rollout state. 2025-07-17T06:38:20.9078538Z  2025-07-17T06:38:20.9079136Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:38:20.9079986Z  and the text below is the list of opted in users. 2025-07-17T06:38:20.9080604Z  2025-07-17T06:38:20.9081266Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:38:20.9082055Z  """ 2025-07-17T06:38:20.9082697Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:38:20.9083552Z  return parse_settings_from_text(settings_text) 2025-07-17T06:38:20.9084166Z  2025-07-17T06:38:20.9084663Z  2025-07-17T06:38:20.9085201Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:38:20.9085857Z  """ 2025-07-17T06:38:20.9086316Z  Parse users from the rollout state. 2025-07-17T06:38:20.9086879Z  2025-07-17T06:38:20.9087257Z  """ 2025-07-17T06:38:20.9087871Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:38:20.9088706Z  return parse_user_opt_in_from_text(users_text) 2025-07-17T06:38:20.9089318Z  2025-07-17T06:38:20.9089678Z  2025-07-17T06:38:20.9090371Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:38:20.9091195Z  """ 2025-07-17T06:38:20.9091681Z  Check if a user is opted into an experiment 2025-07-17T06:38:20.9092271Z  """ 2025-07-17T06:38:20.9092796Z  return experiment_name in user_optins.get(user, []) 2025-07-17T06:38:20.9093437Z  2025-07-17T06:38:20.9093952Z  2025-07-17T06:38:20.9094752Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:38:20.9095590Z  """ 2025-07-17T06:38:20.9096121Z  Check if a user explicitly opted out of an experiment 2025-07-17T06:38:20.9096768Z  """ 2025-07-17T06:38:20.9097352Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:38:20.9098128Z  experiment_optout = "-" + experiment_name 2025-07-17T06:38:20.9098845Z  if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:38:20.9099521Z  return False 2025-07-17T06:38:20.9099991Z  2025-07-17T06:38:20.9100497Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:38:20.9101161Z  log.warning( 2025-07-17T06:38:20.9102075Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:38:20.9103030Z  ) 2025-07-17T06:38:20.9103442Z  2025-07-17T06:38:20.9103836Z  return True 2025-07-17T06:38:20.9104277Z  2025-07-17T06:38:20.9104733Z  2025-07-17T06:38:20.9105140Z def get_runner_prefix( 2025-07-17T06:38:20.9105651Z  rollout_state: str, 2025-07-17T06:38:20.9106205Z  workflow_requestors: Iterable[str], 2025-07-17T06:38:20.9106773Z  branch: str, 2025-07-17T06:38:20.9107375Z  eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:38:20.9108128Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:38:20.9108785Z  is_canary: bool = False, 2025-07-17T06:38:20.9109303Z ) -> str: 2025-07-17T06:38:20.9109828Z  settings = parse_settings(rollout_state) 2025-07-17T06:38:20.9110489Z  user_optins = parse_users(rollout_state) 2025-07-17T06:38:20.9111069Z  2025-07-17T06:38:20.9111596Z  fleet_prefix = "" 2025-07-17T06:38:20.9112103Z  prefixes = [] 2025-07-17T06:38:20.9112843Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:38:20.9113910Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:38:20.9114793Z  log.info( 2025-07-17T06:38:20.9115572Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:38:20.9116378Z  ) 2025-07-17T06:38:20.9116838Z  continue 2025-07-17T06:38:20.9117300Z  2025-07-17T06:38:20.9117723Z  if opt_out_experiments: 2025-07-17T06:38:20.9118349Z  if experiment_name in opt_out_experiments: 2025-07-17T06:38:20.9119075Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:38:20.9119743Z  log.info( 2025-07-17T06:38:20.9120771Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:38:20.9121889Z  ) 2025-07-17T06:38:20.9122356Z  continue 2025-07-17T06:38:20.9122849Z  2025-07-17T06:38:20.9123265Z  if eligible_experiments: 2025-07-17T06:38:20.9123909Z  if experiment_name not in eligible_experiments: 2025-07-17T06:38:20.9124726Z  exp_list = ", ".join(eligible_experiments) 2025-07-17T06:38:20.9125337Z  log.info( 2025-07-17T06:38:20.9126226Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:38:20.9127134Z  ) 2025-07-17T06:38:20.9127737Z  continue 2025-07-17T06:38:20.9128304Z  elif not experiment_settings.default: 2025-07-17T06:38:20.9128895Z  log.info( 2025-07-17T06:38:20.9129652Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:38:20.9130445Z  ) 2025-07-17T06:38:20.9130889Z  continue 2025-07-17T06:38:20.9131345Z  2025-07-17T06:38:20.9131864Z  # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:38:20.9132552Z  opted_out_users = [ 2025-07-17T06:38:20.9133077Z  requestor 2025-07-17T06:38:20.9133630Z  for requestor in workflow_requestors 2025-07-17T06:38:20.9134375Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:38:20.9135173Z  ] 2025-07-17T06:38:20.9135580Z  2025-07-17T06:38:20.9135988Z  if opted_out_users: 2025-07-17T06:38:20.9136537Z  log.info( 2025-07-17T06:38:20.9137249Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:38:20.9138009Z  ) 2025-07-17T06:38:20.9138448Z  continue 2025-07-17T06:38:20.9138913Z  2025-07-17T06:38:20.9139422Z  # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:38:20.9140095Z  opted_in_users = [ 2025-07-17T06:38:20.9140650Z  requestor 2025-07-17T06:38:20.9141196Z  for requestor in workflow_requestors 2025-07-17T06:38:20.9141950Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:38:20.9142627Z  ] 2025-07-17T06:38:20.9143038Z  2025-07-17T06:38:20.9143432Z  enabled = False 2025-07-17T06:38:20.9143962Z  if opted_in_users: 2025-07-17T06:38:20.9144743Z  log.info( 2025-07-17T06:38:20.9145490Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:38:20.9146241Z  ) 2025-07-17T06:38:20.9146695Z  enabled = True 2025-07-17T06:38:20.9147199Z  2025-07-17T06:38:20.9147657Z  elif experiment_settings.rollout_perc: 2025-07-17T06:38:20.9148582Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:38:20.9149642Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:38:20.9150378Z  log.info( 2025-07-17T06:38:20.9151363Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:38:20.9152383Z  ) 2025-07-17T06:38:20.9152877Z  enabled = True 2025-07-17T06:38:20.9153396Z  2025-07-17T06:38:20.9153796Z  if enabled: 2025-07-17T06:38:20.9154309Z  label = experiment_name 2025-07-17T06:38:20.9155052Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:38:20.9155965Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:38:20.9156923Z  # - If it's enabled, then we always list it's prefix first 2025-07-17T06:38:20.9157767Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:38:20.9158495Z  if is_canary: 2025-07-17T06:38:20.9159069Z  label += CANARY_FLEET_SUFFIX 2025-07-17T06:38:20.9159680Z  fleet_prefix = label 2025-07-17T06:38:20.9160236Z  else: 2025-07-17T06:38:20.9160893Z  prefixes.append(label) 2025-07-17T06:38:20.9161452Z  2025-07-17T06:38:20.9161854Z  if len(prefixes) > 1: 2025-07-17T06:38:20.9162381Z  log.error( 2025-07-17T06:38:20.9163531Z  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:38:20.9164846Z  ) 2025-07-17T06:38:20.9165295Z  prefixes = prefixes[:1] 2025-07-17T06:38:20.9165827Z  2025-07-17T06:38:20.9166233Z  # Fleet always comes first 2025-07-17T06:38:20.9166785Z  if fleet_prefix: 2025-07-17T06:38:20.9167323Z  prefixes.insert(0, fleet_prefix) 2025-07-17T06:38:20.9167886Z  2025-07-17T06:38:20.9168387Z  return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:38:20.9169010Z  2025-07-17T06:38:20.9169385Z  2025-07-17T06:38:20.9170083Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:38:20.9170930Z  """ 2025-07-17T06:38:20.9171582Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:38:20.9172358Z  2025-07-17T06:38:20.9172995Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:38:20.9173775Z  """ 2025-07-17T06:38:20.9174235Z  gh = get_gh_client(github_token) 2025-07-17T06:38:20.9174959Z  issue = get_issue(gh, repo, issue_num) 2025-07-17T06:38:20.9175685Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:38:20.9176337Z  2025-07-17T06:38:20.9176715Z  2025-07-17T06:38:20.9177379Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:38:20.9178451Z  for _ in range(num_retries): 2025-07-17T06:38:20.9179003Z  try: 2025-07-17T06:38:20.9179513Z  req = Request(url=url, headers=headers) 2025-07-17T06:38:20.9180250Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:38:20.9180966Z  return json.loads(content) 2025-07-17T06:38:20.9181568Z  except Exception as e: 2025-07-17T06:38:20.9182200Z  log.warning(f"Could not download {url}: {e}") 2025-07-17T06:38:20.9182806Z  2025-07-17T06:38:20.9183438Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:38:20.9184221Z  return {} 2025-07-17T06:38:20.9184774Z  2025-07-17T06:38:20.9185146Z  2025-07-17T06:38:20.9185521Z @cache 2025-07-17T06:38:20.9186219Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:38:20.9187055Z  """ 2025-07-17T06:38:20.9187513Z  Dynamically get PR information 2025-07-17T06:38:20.9188054Z  """ 2025-07-17T06:38:20.9188630Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:38:20.9189318Z  headers = { 2025-07-17T06:38:20.9189862Z  "Accept": "application/vnd.github.v3+json", 2025-07-17T06:38:20.9190536Z  "Authorization": f"token {github_token}", 2025-07-17T06:38:20.9191127Z  } 2025-07-17T06:38:20.9191633Z  json_response: dict[str, Any] = download_json( 2025-07-17T06:38:20.9192303Z  url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:38:20.9192904Z  headers=headers, 2025-07-17T06:38:20.9193393Z  ) 2025-07-17T06:38:20.9193800Z  2025-07-17T06:38:20.9194208Z  if not json_response: 2025-07-17T06:38:20.9194977Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:38:20.9195801Z  return {} 2025-07-17T06:38:20.9196266Z  2025-07-17T06:38:20.9196671Z  return json_response 2025-07-17T06:38:20.9197170Z  2025-07-17T06:38:20.9197543Z  2025-07-17T06:38:20.9198181Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:38:20.9198983Z  """ 2025-07-17T06:38:20.9199578Z  Dynamically get the latest list of labels from the pull request 2025-07-17T06:38:20.9200302Z  """ 2025-07-17T06:38:20.9200848Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:38:20.9201527Z  return { 2025-07-17T06:38:20.9202217Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:38:20.9202962Z  } 2025-07-17T06:38:20.9203364Z  2025-07-17T06:38:20.9203737Z  2025-07-17T06:38:20.9204130Z def main() -> None: 2025-07-17T06:38:20.9204722Z  args = parse_args() 2025-07-17T06:38:20.9205213Z  2025-07-17T06:38:20.9205671Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:38:20.9206260Z  2025-07-17T06:38:20.9206673Z  # Check if the PR is opt-out 2025-07-17T06:38:20.9207228Z  if args.pr_number: 2025-07-17T06:38:20.9207999Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:38:20.9208822Z  if OPT_OUT_LABEL in labels: 2025-07-17T06:38:20.9209387Z  log.info( 2025-07-17T06:38:20.9210203Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:38:20.9211040Z  ) 2025-07-17T06:38:20.9211697Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:38:20.9212444Z  sys.exit() 2025-07-17T06:38:20.9213042Z  2025-07-17T06:38:20.9213424Z  try: 2025-07-17T06:38:20.9213937Z  rollout_state = get_rollout_state_from_issue( 2025-07-17T06:38:20.9214833Z  args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:38:20.9215544Z  ) 2025-07-17T06:38:20.9215960Z  2025-07-17T06:38:20.9216391Z  username = get_potential_pr_author( 2025-07-17T06:38:20.9216995Z  args.github_token, 2025-07-17T06:38:20.9217562Z  args.github_repo, 2025-07-17T06:38:20.9218109Z  args.github_actor, 2025-07-17T06:38:20.9218672Z  args.github_ref_type, 2025-07-17T06:38:20.9219238Z  args.github_branch, 2025-07-17T06:38:20.9219757Z  ) 2025-07-17T06:38:20.9220179Z  2025-07-17T06:38:20.9220725Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:38:20.9221392Z  2025-07-17T06:38:20.9221861Z  runner_label_prefix = get_runner_prefix( 2025-07-17T06:38:20.9222470Z  rollout_state, 2025-07-17T06:38:20.9223047Z  (args.github_issue_owner, username), 2025-07-17T06:38:20.9223649Z  args.github_branch, 2025-07-17T06:38:20.9224227Z  args.eligible_experiments, 2025-07-17T06:38:20.9224937Z  args.opt_out_experiments, 2025-07-17T06:38:20.9225505Z  is_canary, 2025-07-17T06:38:20.9225996Z  ) 2025-07-17T06:38:20.9226400Z  2025-07-17T06:38:20.9226806Z  except Exception as e: 2025-07-17T06:38:20.9227326Z  log.error( 2025-07-17T06:38:20.9228103Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:38:20.9229045Z  ) 2025-07-17T06:38:20.9229466Z  2025-07-17T06:38:20.9230040Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:38:20.9230753Z  2025-07-17T06:38:20.9231120Z  2025-07-17T06:38:20.9231508Z if __name__ == "__main__": 2025-07-17T06:38:20.9232023Z  main() 2025-07-17T06:38:20.9232438Z  2025-07-17T06:38:20.9232804Z EOF 2025-07-17T06:38:20.9233183Z  2025-07-17T06:38:20.9233591Z cat runner_determinator.py 2025-07-17T06:38:20.9440583Z shell: /usr/bin/bash -e {0} 2025-07-17T06:38:20.9441736Z env: 2025-07-17T06:38:20.9442532Z GITHUB_TOKEN: *** 2025-07-17T06:38:20.9442983Z ISSUE_NUMBER: 5132 2025-07-17T06:38:20.9443444Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:38:20.9443973Z ISSUE_OWNER: 2025-07-17T06:38:20.9444394Z CHECK_EXPERIMENTS: 2025-07-17T06:38:20.9445004Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:38:20.9445440Z PR_NUMBER: 2025-07-17T06:38:20.9445863Z ##[endgroup] 2025-07-17T06:38:20.9652888Z # flake8: noqa: G004 2025-07-17T06:38:20.9653282Z 2025-07-17T06:38:20.9653720Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:38:20.9655015Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:38:20.9655899Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:38:20.9656377Z 2025-07-17T06:38:20.9656540Z """ 2025-07-17T06:38:20.9657145Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:38:20.9658051Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:38:20.9658983Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:38:20.9659826Z of which runners should be used to run which job. 2025-07-17T06:38:20.9660260Z 2025-07-17T06:38:20.9660645Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:38:20.9661803Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:38:20.9662729Z settings are considered to be empty with only the second part, the user 2025-07-17T06:38:20.9663470Z list, defined. 2025-07-17T06:38:20.9663722Z 2025-07-17T06:38:20.9664088Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:38:20.9665317Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:38:20.9666165Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:38:20.9666641Z 2025-07-17T06:38:20.9667015Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:38:20.9667907Z The user list is also a comma separated list of additional features or 2025-07-17T06:38:20.9668678Z experiments which the user could be opted in to. 2025-07-17T06:38:20.9669101Z 2025-07-17T06:38:20.9669309Z The user list has the following rules: 2025-07-17T06:38:20.9669687Z 2025-07-17T06:38:20.9670025Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:38:20.9670940Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:38:20.9671743Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:38:20.9672166Z 2025-07-17T06:38:20.9672343Z Example config: 2025-07-17T06:38:20.9672832Z # A list of experiments that can be opted into. 2025-07-17T06:38:20.9673527Z # This defines the behavior they'll induce when opted into. 2025-07-17T06:38:20.9674184Z # Expected syntax is: 2025-07-17T06:38:20.9675077Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:38:20.9676095Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:38:20.9676728Z 2025-07-17T06:38:20.9676908Z experiments: 2025-07-17T06:38:20.9677331Z lf: 2025-07-17T06:38:20.9677742Z rollout_percent: 25 2025-07-17T06:38:20.9678435Z all_branches: false 2025-07-17T06:38:20.9678921Z default: true 2025-07-17T06:38:20.9679364Z --- 2025-07-17T06:38:20.9679585Z 2025-07-17T06:38:20.9679761Z # Opt-ins: 2025-07-17T06:38:20.9680363Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:38:20.9681254Z # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:38:20.9682060Z # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:38:20.9682785Z # Experiments should be from the above list. 2025-07-17T06:38:20.9683199Z 2025-07-17T06:38:20.9683395Z @User1,-lf,split_build 2025-07-17T06:38:20.9683866Z @User2,lf 2025-07-17T06:38:20.9684281Z @User3,split_build 2025-07-17T06:38:20.9684894Z """ 2025-07-17T06:38:20.9685119Z 2025-07-17T06:38:20.9685289Z import json 2025-07-17T06:38:20.9685681Z import logging 2025-07-17T06:38:20.9686095Z import os 2025-07-17T06:38:20.9686480Z import random 2025-07-17T06:38:20.9686888Z import re 2025-07-17T06:38:20.9687288Z import sys 2025-07-17T06:38:20.9687722Z from argparse import ArgumentParser 2025-07-17T06:38:20.9688302Z from collections.abc import Iterable 2025-07-17T06:38:20.9688866Z from functools import cache 2025-07-17T06:38:20.9689376Z from logging import LogRecord 2025-07-17T06:38:20.9689894Z from typing import Any, NamedTuple 2025-07-17T06:38:20.9690458Z from urllib.request import Request, urlopen 2025-07-17T06:38:20.9690860Z 2025-07-17T06:38:20.9691028Z import yaml 2025-07-17T06:38:20.9691453Z from github import Auth, Github 2025-07-17T06:38:20.9691967Z from github.Issue import Issue 2025-07-17T06:38:20.9692292Z 2025-07-17T06:38:20.9692299Z 2025-07-17T06:38:20.9692522Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:38:20.9693244Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:38:20.9694132Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:38:20.9694934Z 2025-07-17T06:38:20.9695184Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:38:20.9695956Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:38:20.9696525Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:38:20.9697112Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:38:20.9697491Z 2025-07-17T06:38:20.9697693Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:38:20.9698056Z 2025-07-17T06:38:20.9698256Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:38:20.9698746Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:38:20.9699062Z 2025-07-17T06:38:20.9699077Z 2025-07-17T06:38:20.9699270Z class Experiment(NamedTuple): 2025-07-17T06:38:20.9699772Z rollout_perc: float = ( 2025-07-17T06:38:20.9700451Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:38:20.9701164Z ) 2025-07-17T06:38:20.9701560Z all_branches: bool = ( 2025-07-17T06:38:20.9702225Z False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:38:20.9702928Z ) 2025-07-17T06:38:20.9703329Z default: bool = ( 2025-07-17T06:38:20.9703925Z True # If True, the experiment is enabled by default for all queries 2025-07-17T06:38:20.9704807Z ) 2025-07-17T06:38:20.9705102Z 2025-07-17T06:38:20.9705292Z # Add more fields as needed 2025-07-17T06:38:20.9705623Z 2025-07-17T06:38:20.9705630Z 2025-07-17T06:38:20.9705817Z class Settings(NamedTuple): 2025-07-17T06:38:20.9706296Z """ 2025-07-17T06:38:20.9706773Z Settings for the experiments that can be opted into. 2025-07-17T06:38:20.9707380Z """ 2025-07-17T06:38:20.9707598Z 2025-07-17T06:38:20.9707810Z experiments: dict[str, Experiment] = {} 2025-07-17T06:38:20.9708195Z 2025-07-17T06:38:20.9708207Z 2025-07-17T06:38:20.9708419Z class ColorFormatter(logging.Formatter): 2025-07-17T06:38:20.9709071Z """Color codes the log messages based on the log level""" 2025-07-17T06:38:20.9709539Z 2025-07-17T06:38:20.9709709Z COLORS = { 2025-07-17T06:38:20.9710148Z "WARNING": "\033[33m", # Yellow 2025-07-17T06:38:20.9710850Z "ERROR": "\033[31m", # Red 2025-07-17T06:38:20.9711380Z "CRITICAL": "\033[31m", # Red 2025-07-17T06:38:20.9711903Z "INFO": "\033[0m", # Reset 2025-07-17T06:38:20.9712420Z "DEBUG": "\033[0m", # Reset 2025-07-17T06:38:20.9712924Z } 2025-07-17T06:38:20.9713149Z 2025-07-17T06:38:20.9713375Z def format(self, record: LogRecord) -> str: 2025-07-17T06:38:20.9714162Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:38:20.9715164Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:38:20.9715775Z return super().format(record) 2025-07-17T06:38:20.9716131Z 2025-07-17T06:38:20.9716138Z 2025-07-17T06:38:20.9716349Z handler = logging.StreamHandler() 2025-07-17T06:38:20.9717086Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:38:20.9717660Z 2025-07-17T06:38:20.9717911Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:38:20.9718532Z log.addHandler(handler) 2025-07-17T06:38:20.9719005Z log.setLevel(logging.INFO) 2025-07-17T06:38:20.9719318Z 2025-07-17T06:38:20.9719324Z 2025-07-17T06:38:20.9719580Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:38:20.9720171Z """ 2025-07-17T06:38:20.9720702Z Defines outputs of the github action that invokes this script 2025-07-17T06:38:20.9721365Z """ 2025-07-17T06:38:20.9721757Z if not GITHUB_OUTPUT: 2025-07-17T06:38:20.9722853Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:38:20.9724008Z log.warning( 2025-07-17T06:38:20.9725075Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-07-17T06:38:20.9726045Z ) 2025-07-17T06:38:20.9736819Z print(f"::set-output name={key}::{value}") 2025-07-17T06:38:20.9737479Z return 2025-07-17T06:38:20.9737736Z 2025-07-17T06:38:20.9738180Z with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:38:20.9738843Z log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:38:20.9739455Z f.write(f"{key}={value}\n") 2025-07-17T06:38:20.9739824Z 2025-07-17T06:38:20.9739833Z 2025-07-17T06:38:20.9740142Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:38:20.9740820Z return frozenset( 2025-07-17T06:38:20.9741462Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:38:20.9742185Z ) 2025-07-17T06:38:20.9742405Z 2025-07-17T06:38:20.9742413Z 2025-07-17T06:38:20.9742598Z def parse_args() -> Any: 2025-07-17T06:38:20.9743183Z parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:38:20.9744087Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:38:20.9745185Z parser.add_argument( 2025-07-17T06:38:20.9745680Z "--github-issue-repo", 2025-07-17T06:38:20.9746184Z type=str, 2025-07-17T06:38:20.9746624Z required=False, 2025-07-17T06:38:20.9747098Z default="pytorch/test-infra", 2025-07-17T06:38:20.9747655Z help="GitHub repo to get the issue", 2025-07-17T06:38:20.9748188Z ) 2025-07-17T06:38:20.9748578Z parser.add_argument( 2025-07-17T06:38:20.9749060Z "--github-repo", 2025-07-17T06:38:20.9749511Z type=str, 2025-07-17T06:38:20.9749934Z required=True, 2025-07-17T06:38:20.9750433Z help="GitHub repo where CI is running", 2025-07-17T06:38:20.9751000Z ) 2025-07-17T06:38:20.9751383Z parser.add_argument( 2025-07-17T06:38:20.9752275Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:38:20.9752965Z ) 2025-07-17T06:38:20.9753358Z parser.add_argument( 2025-07-17T06:38:20.9753998Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:38:20.9754892Z ) 2025-07-17T06:38:20.9755281Z parser.add_argument( 2025-07-17T06:38:20.9756133Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:38:20.9756871Z ) 2025-07-17T06:38:20.9757252Z parser.add_argument( 2025-07-17T06:38:20.9757963Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:38:20.9758727Z ) 2025-07-17T06:38:20.9812336Z parser.add_argument( 2025-07-17T06:38:20.9813356Z "--github-ref-type", 2025-07-17T06:38:20.9814230Z type=str, 2025-07-17T06:38:20.9815053Z required=True, 2025-07-17T06:38:20.9815598Z help="Current GitHub ref type, branch or tag", 2025-07-17T06:38:20.9816202Z ) 2025-07-17T06:38:20.9816612Z parser.add_argument( 2025-07-17T06:38:20.9817113Z "--eligible-experiments", 2025-07-17T06:38:20.9817669Z type=_str_comma_separated_to_set, 2025-07-17T06:38:20.9818224Z required=False, 2025-07-17T06:38:20.9818671Z default="", 2025-07-17T06:38:20.9819580Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:38:20.9820543Z ) 2025-07-17T06:38:20.9820937Z parser.add_argument( 2025-07-17T06:38:20.9821439Z "--opt-out-experiments", 2025-07-17T06:38:20.9821965Z type=_str_comma_separated_to_set, 2025-07-17T06:38:20.9822510Z required=False, 2025-07-17T06:38:20.9822949Z default="", 2025-07-17T06:38:20.9823378Z help=( 2025-07-17T06:38:20.9824072Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:38:20.9825477Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:38:20.9826341Z ), 2025-07-17T06:38:20.9826722Z ) 2025-07-17T06:38:20.9827121Z parser.add_argument( 2025-07-17T06:38:20.9827585Z "--pr-number", 2025-07-17T06:38:20.9828036Z type=str, 2025-07-17T06:38:20.9828459Z required=False, 2025-07-17T06:38:20.9828920Z default="", 2025-07-17T06:38:20.9829671Z help="the optional PR number where this is run", 2025-07-17T06:38:20.9830283Z ) 2025-07-17T06:38:20.9830504Z 2025-07-17T06:38:20.9830709Z return parser.parse_args() 2025-07-17T06:38:20.9831042Z 2025-07-17T06:38:20.9831049Z 2025-07-17T06:38:20.9831455Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:38:20.9832245Z auth = Auth.Token(github_token) 2025-07-17T06:38:20.9832775Z return Github(auth=auth) 2025-07-17T06:38:20.9833095Z 2025-07-17T06:38:20.9833102Z 2025-07-17T06:38:20.9833556Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:38:20.9834386Z repo = gh.get_repo(repo) 2025-07-17T06:38:20.9835154Z return repo.get_issue(number=issue_num) 2025-07-17T06:38:20.9835551Z 2025-07-17T06:38:20.9835558Z 2025-07-17T06:38:20.9835758Z def get_potential_pr_author( 2025-07-17T06:38:20.9836434Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:38:20.9837150Z ) -> str: 2025-07-17T06:38:20.9837685Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:38:20.9838511Z # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:38:20.9839278Z # embedded in the tag name: ciflow// 2025-07-17T06:38:20.9839714Z 2025-07-17T06:38:20.9839904Z gh = get_gh_client(github_token) 2025-07-17T06:38:20.9840259Z 2025-07-17T06:38:20.9840531Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:38:20.9841179Z split_tag = ref_name.split("/") 2025-07-17T06:38:20.9841707Z if ( 2025-07-17T06:38:20.9842127Z len(split_tag) == 3 2025-07-17T06:38:20.9842639Z and split_tag[0] == "ciflow" 2025-07-17T06:38:20.9843192Z and split_tag[2].isnumeric() 2025-07-17T06:38:20.9843717Z ): 2025-07-17T06:38:20.9844133Z pr_number = split_tag[2] 2025-07-17T06:38:20.9844994Z try: 2025-07-17T06:38:20.9845467Z repository = gh.get_repo(repo) 2025-07-17T06:38:20.9846104Z pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:38:20.9846741Z except Exception as e: 2025-07-17T06:38:20.9847280Z raise Exception( # noqa: TRY002 2025-07-17T06:38:20.9847969Z f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:38:20.9848635Z ) from e 2025-07-17T06:38:20.9849197Z return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:38:20.9849915Z # In all other cases, return the original input username 2025-07-17T06:38:20.9850518Z return username 2025-07-17T06:38:20.9850789Z 2025-07-17T06:38:20.9850794Z 2025-07-17T06:38:20.9851028Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:38:20.9851583Z """ 2025-07-17T06:38:20.9852250Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:38:20.9853048Z """ 2025-07-17T06:38:20.9853616Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:38:20.9854159Z 2025-07-17T06:38:20.9854165Z 2025-07-17T06:38:20.9854366Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:38:20.9855101Z try: 2025-07-17T06:38:20.9855530Z data = yaml.safe_load(yaml_text) 2025-07-17T06:38:20.9856067Z return data 2025-07-17T06:38:20.9856509Z except yaml.YAMLError: 2025-07-17T06:38:20.9857000Z log.exception("Error loading YAML") 2025-07-17T06:38:20.9857545Z raise 2025-07-17T06:38:20.9857780Z 2025-07-17T06:38:20.9857787Z 2025-07-17T06:38:20.9858219Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:38:20.9858984Z """ 2025-07-17T06:38:20.9859633Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:38:20.9860248Z 2025-07-17T06:38:20.9860755Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:38:20.9861555Z and the text below is the list of opted in users. 2025-07-17T06:38:20.9861986Z 2025-07-17T06:38:20.9862362Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:38:20.9863093Z """ 2025-07-17T06:38:20.9863552Z rollout_state_parts = rollout_state.split("---") 2025-07-17T06:38:20.9864184Z if len(rollout_state_parts) >= 2: 2025-07-17T06:38:20.9865483Z return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:38:20.9866114Z else: 2025-07-17T06:38:20.9866523Z return "", rollout_state 2025-07-17T06:38:20.9866853Z 2025-07-17T06:38:20.9866859Z 2025-07-17T06:38:20.9867061Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:38:20.9867591Z """ 2025-07-17T06:38:20.9868127Z Dictionary of users with a list of features they have opted into 2025-07-17T06:38:20.9868806Z """ 2025-07-17T06:38:20.9869030Z 2025-07-17T06:38:20.9869037Z 2025-07-17T06:38:20.9869388Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:38:20.9870064Z """ 2025-07-17T06:38:20.9870794Z 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:38:20.9871502Z 2025-07-17T06:38:20.9872112Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:38:20.9873127Z - Example line: "@User1,lf,split_build" 2025-07-17T06:38:20.9873845Z - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:38:20.9874350Z 2025-07-17T06:38:20.9874357Z 2025-07-17T06:38:20.9874518Z """ 2025-07-17T06:38:20.9875043Z optins = UserOptins() 2025-07-17T06:38:20.9875549Z for user in user_optin_text.split("\n"): 2025-07-17T06:38:20.9876132Z user = user.strip("\r\n\t -") 2025-07-17T06:38:20.9876702Z if not user or not user.startswith("@"): 2025-07-17T06:38:20.9877460Z # Not a valid user. Skip 2025-07-17T06:38:20.9877966Z continue 2025-07-17T06:38:20.9878239Z 2025-07-17T06:38:20.9878399Z if user: 2025-07-17T06:38:20.9878866Z usr_name = user.split(",")[0].strip("@") 2025-07-17T06:38:20.9879575Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:38:20.9880106Z 2025-07-17T06:38:20.9880273Z return optins 2025-07-17T06:38:20.9880522Z 2025-07-17T06:38:20.9880528Z 2025-07-17T06:38:20.9880811Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:38:20.9881431Z """ 2025-07-17T06:38:20.9881854Z Check if the experiment name is valid. 2025-07-17T06:38:20.9882392Z A valid name: 2025-07-17T06:38:20.9883080Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:38:20.9884073Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:38:20.9884946Z - Cannot contain spaces 2025-07-17T06:38:20.9885431Z """ 2025-07-17T06:38:20.9885655Z 2025-07-17T06:38:20.9885916Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:38:20.9886641Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:38:20.9887109Z 2025-07-17T06:38:20.9887273Z if valid: 2025-07-17T06:38:20.9887682Z return True 2025-07-17T06:38:20.9887941Z 2025-07-17T06:38:20.9888107Z log.error( 2025-07-17T06:38:20.9889588Z 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:38:20.9891182Z ) 2025-07-17T06:38:20.9891563Z return False 2025-07-17T06:38:20.9891819Z 2025-07-17T06:38:20.9891825Z 2025-07-17T06:38:20.9892133Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:38:20.9892774Z """ 2025-07-17T06:38:20.9893516Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:38:20.9894274Z """ 2025-07-17T06:38:20.9894752Z try: 2025-07-17T06:38:20.9895152Z if settings_text: 2025-07-17T06:38:20.9895902Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:38:20.9896715Z # for easy reading 2025-07-17T06:38:20.9897534Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:38:20.9898445Z # the backtick character in shell commands. 2025-07-17T06:38:20.9899067Z backtick = chr(96) # backtick character 2025-07-17T06:38:20.9899758Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:38:20.9900450Z settings = load_yaml(settings_text) 2025-07-17T06:38:20.9900861Z 2025-07-17T06:38:20.9901279Z # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:38:20.9902073Z experiments = {} 2025-07-17T06:38:20.9902396Z 2025-07-17T06:38:20.9902788Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:38:20.9903595Z if not is_valid_experiment_name(exp_name): 2025-07-17T06:38:20.9904811Z # 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:38:20.9905883Z continue 2025-07-17T06:38:20.9906191Z 2025-07-17T06:38:20.9906378Z valid_settings = {} 2025-07-17T06:38:20.9906921Z for setting in exp_settings: 2025-07-17T06:38:20.9907521Z if setting not in Experiment._fields: 2025-07-17T06:38:20.9908104Z log.warning( 2025-07-17T06:38:20.9908842Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:38:20.9909751Z ) 2025-07-17T06:38:20.9910218Z else: 2025-07-17T06:38:20.9910749Z valid_settings[setting] = exp_settings[setting] 2025-07-17T06:38:20.9911202Z 2025-07-17T06:38:20.9911483Z experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:38:20.9912141Z return Settings(experiments) 2025-07-17T06:38:20.9912517Z 2025-07-17T06:38:20.9912696Z except Exception: 2025-07-17T06:38:20.9913199Z log.exception("Failed to parse settings") 2025-07-17T06:38:20.9913612Z 2025-07-17T06:38:20.9913785Z return Settings() 2025-07-17T06:38:20.9914067Z 2025-07-17T06:38:20.9914075Z 2025-07-17T06:38:20.9914319Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:38:20.9915024Z """ 2025-07-17T06:38:20.9915486Z Parse settings, if any, from the rollout state. 2025-07-17T06:38:20.9915917Z 2025-07-17T06:38:20.9916274Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:38:20.9917059Z and the text below is the list of opted in users. 2025-07-17T06:38:20.9917487Z 2025-07-17T06:38:20.9917900Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:38:20.9918657Z """ 2025-07-17T06:38:20.9919230Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:38:20.9920008Z return parse_settings_from_text(settings_text) 2025-07-17T06:38:20.9920434Z 2025-07-17T06:38:20.9920440Z 2025-07-17T06:38:20.9920688Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:38:20.9921278Z """ 2025-07-17T06:38:20.9921674Z Parse users from the rollout state. 2025-07-17T06:38:20.9922060Z 2025-07-17T06:38:20.9922224Z """ 2025-07-17T06:38:20.9922767Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:38:20.9923560Z return parse_user_opt_in_from_text(users_text) 2025-07-17T06:38:20.9923995Z 2025-07-17T06:38:20.9924002Z 2025-07-17T06:38:20.9924655Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:38:20.9925454Z """ 2025-07-17T06:38:20.9925893Z Check if a user is opted into an experiment 2025-07-17T06:38:20.9926443Z """ 2025-07-17T06:38:20.9926926Z return experiment_name in user_optins.get(user, []) 2025-07-17T06:38:20.9927380Z 2025-07-17T06:38:20.9927386Z 2025-07-17T06:38:20.9927804Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:38:20.9928577Z """ 2025-07-17T06:38:20.9929057Z Check if a user explicitly opted out of an experiment 2025-07-17T06:38:20.9929661Z """ 2025-07-17T06:38:20.9930180Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:38:20.9930880Z experiment_optout = "-" + experiment_name 2025-07-17T06:38:20.9931544Z if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:38:20.9932165Z return False 2025-07-17T06:38:20.9932446Z 2025-07-17T06:38:20.9932723Z if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:38:20.9933344Z log.warning( 2025-07-17T06:38:20.9934170Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:38:20.9935184Z ) 2025-07-17T06:38:20.9935417Z 2025-07-17T06:38:20.9935584Z return True 2025-07-17T06:38:20.9935833Z 2025-07-17T06:38:20.9935839Z 2025-07-17T06:38:20.9936020Z def get_runner_prefix( 2025-07-17T06:38:20.9936466Z rollout_state: str, 2025-07-17T06:38:20.9936940Z workflow_requestors: Iterable[str], 2025-07-17T06:38:20.9937471Z branch: str, 2025-07-17T06:38:20.9937985Z eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:38:20.9938666Z opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:38:20.9939277Z is_canary: bool = False, 2025-07-17T06:38:20.9939754Z ) -> str: 2025-07-17T06:38:20.9940321Z settings = parse_settings(rollout_state) 2025-07-17T06:38:20.9940942Z user_optins = parse_users(rollout_state) 2025-07-17T06:38:20.9941342Z 2025-07-17T06:38:20.9941525Z fleet_prefix = "" 2025-07-17T06:38:20.9941971Z prefixes = [] 2025-07-17T06:38:20.9942613Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:38:20.9943588Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:38:20.9944330Z log.info( 2025-07-17T06:38:20.9945140Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:38:20.9945923Z ) 2025-07-17T06:38:20.9946316Z continue 2025-07-17T06:38:20.9946586Z 2025-07-17T06:38:20.9946777Z if opt_out_experiments: 2025-07-17T06:38:20.9947334Z if experiment_name in opt_out_experiments: 2025-07-17T06:38:20.9948004Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:38:20.9948609Z log.info( 2025-07-17T06:38:20.9949562Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:38:20.9950566Z ) 2025-07-17T06:38:20.9950972Z continue 2025-07-17T06:38:20.9951259Z 2025-07-17T06:38:20.9951453Z if eligible_experiments: 2025-07-17T06:38:20.9952062Z if experiment_name not in eligible_experiments: 2025-07-17T06:38:20.9952730Z exp_list = ", ".join(eligible_experiments) 2025-07-17T06:38:20.9953308Z log.info( 2025-07-17T06:38:20.9954107Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:38:20.9955069Z ) 2025-07-17T06:38:20.9955480Z continue 2025-07-17T06:38:20.9955978Z elif not experiment_settings.default: 2025-07-17T06:38:20.9956528Z log.info( 2025-07-17T06:38:20.9957338Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:38:20.9958109Z ) 2025-07-17T06:38:20.9958507Z continue 2025-07-17T06:38:20.9958771Z 2025-07-17T06:38:20.9959046Z # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:38:20.9959686Z opted_out_users = [ 2025-07-17T06:38:20.9960156Z requestor 2025-07-17T06:38:20.9960623Z for requestor in workflow_requestors 2025-07-17T06:38:20.9961321Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:38:20.9961966Z ] 2025-07-17T06:38:20.9962189Z 2025-07-17T06:38:20.9962368Z if opted_out_users: 2025-07-17T06:38:20.9962833Z log.info( 2025-07-17T06:38:20.9963467Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:38:20.9964192Z ) 2025-07-17T06:38:20.9964685Z continue 2025-07-17T06:38:20.9964960Z 2025-07-17T06:38:20.9965252Z # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:38:20.9965908Z opted_in_users = [ 2025-07-17T06:38:20.9966383Z requestor 2025-07-17T06:38:20.9966852Z for requestor in workflow_requestors 2025-07-17T06:38:20.9967541Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:38:20.9968179Z ] 2025-07-17T06:38:20.9968407Z 2025-07-17T06:38:20.9968582Z enabled = False 2025-07-17T06:38:20.9969049Z if opted_in_users: 2025-07-17T06:38:20.9969512Z log.info( 2025-07-17T06:38:20.9970131Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:38:20.9970833Z ) 2025-07-17T06:38:20.9971243Z enabled = True 2025-07-17T06:38:20.9971542Z 2025-07-17T06:38:20.9971757Z elif experiment_settings.rollout_perc: 2025-07-17T06:38:20.9972613Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:38:20.9973697Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:38:20.9974380Z log.info( 2025-07-17T06:38:20.9975374Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:38:20.9976321Z ) 2025-07-17T06:38:20.9976751Z enabled = True 2025-07-17T06:38:20.9977069Z 2025-07-17T06:38:20.9977234Z if enabled: 2025-07-17T06:38:20.9977678Z label = experiment_name 2025-07-17T06:38:20.9978245Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:38:20.9979108Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:38:20.9980019Z # - If it's enabled, then we always list it's prefix first 2025-07-17T06:38:20.9980801Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:38:20.9981504Z if is_canary: 2025-07-17T06:38:20.9982015Z label += CANARY_FLEET_SUFFIX 2025-07-17T06:38:20.9982584Z fleet_prefix = label 2025-07-17T06:38:20.9983119Z else: 2025-07-17T06:38:20.9983567Z prefixes.append(label) 2025-07-17T06:38:20.9983931Z 2025-07-17T06:38:20.9984119Z if len(prefixes) > 1: 2025-07-17T06:38:20.9984670Z log.error( 2025-07-17T06:38:20.9985726Z 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:38:20.9986861Z ) 2025-07-17T06:38:20.9987263Z prefixes = prefixes[:1] 2025-07-17T06:38:20.9987589Z 2025-07-17T06:38:20.9987773Z # Fleet always comes first 2025-07-17T06:38:20.9988267Z if fleet_prefix: 2025-07-17T06:38:20.9988736Z prefixes.insert(0, fleet_prefix) 2025-07-17T06:38:20.9989145Z 2025-07-17T06:38:20.9989525Z return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:38:20.9989971Z 2025-07-17T06:38:20.9989978Z 2025-07-17T06:38:20.9990439Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:38:20.9991240Z """ 2025-07-17T06:38:20.9991844Z Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:38:20.9992427Z 2025-07-17T06:38:20.9992815Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:38:20.9993549Z """ 2025-07-17T06:38:20.9993955Z gh = get_gh_client(github_token) 2025-07-17T06:38:20.9994520Z issue = get_issue(gh, repo, issue_num) 2025-07-17T06:38:20.9995279Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:38:20.9995750Z 2025-07-17T06:38:20.9995757Z 2025-07-17T06:38:20.9996156Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:38:20.9996949Z for _ in range(num_retries): 2025-07-17T06:38:20.9997440Z try: 2025-07-17T06:38:20.9997883Z req = Request(url=url, headers=headers) 2025-07-17T06:38:20.9998569Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:38:20.9999254Z return json.loads(content) 2025-07-17T06:38:20.9999804Z except Exception as e: 2025-07-17T06:38:21.0000354Z log.warning(f"Could not download {url}: {e}") 2025-07-17T06:38:21.0000785Z 2025-07-17T06:38:21.0001167Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:38:21.0001899Z return {} 2025-07-17T06:38:21.0002144Z 2025-07-17T06:38:21.0002150Z 2025-07-17T06:38:21.0002307Z @cache 2025-07-17T06:38:21.0002944Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:38:21.0003721Z """ 2025-07-17T06:38:21.0004127Z Dynamically get PR information 2025-07-17T06:38:21.0004731Z """ 2025-07-17T06:38:21.0005403Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:38:21.0006060Z headers = { 2025-07-17T06:38:21.0006535Z "Accept": "application/vnd.github.v3+json", 2025-07-17T06:38:21.0007158Z "Authorization": f"token {github_token}", 2025-07-17T06:38:21.0007722Z } 2025-07-17T06:38:21.0008160Z json_response: dict[str, Any] = download_json( 2025-07-17T06:38:21.0008795Z url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:38:21.0009362Z headers=headers, 2025-07-17T06:38:21.0009805Z ) 2025-07-17T06:38:21.0010022Z 2025-07-17T06:38:21.0010213Z if not json_response: 2025-07-17T06:38:21.0010795Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:38:21.0011440Z return {} 2025-07-17T06:38:21.0011693Z 2025-07-17T06:38:21.0011874Z return json_response 2025-07-17T06:38:21.0012180Z 2025-07-17T06:38:21.0012186Z 2025-07-17T06:38:21.0012593Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:38:21.0013360Z """ 2025-07-17T06:38:21.0013894Z Dynamically get the latest list of labels from the pull request 2025-07-17T06:38:21.0014678Z """ 2025-07-17T06:38:21.0015181Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:38:21.0015815Z return { 2025-07-17T06:38:21.0016423Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:38:21.0017149Z } 2025-07-17T06:38:21.0017373Z 2025-07-17T06:38:21.0017380Z 2025-07-17T06:38:21.0017553Z def main() -> None: 2025-07-17T06:38:21.0017988Z args = parse_args() 2025-07-17T06:38:21.0018276Z 2025-07-17T06:38:21.0018499Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:38:21.0018909Z 2025-07-17T06:38:21.0019101Z # Check if the PR is opt-out 2025-07-17T06:38:21.0019607Z if args.pr_number: 2025-07-17T06:38:21.0020277Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:38:21.0021189Z if OPT_OUT_LABEL in labels: 2025-07-17T06:38:21.0021715Z log.info( 2025-07-17T06:38:21.0022425Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:38:21.0023222Z ) 2025-07-17T06:38:21.0023777Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:38:21.0024481Z sys.exit() 2025-07-17T06:38:21.0024859Z 2025-07-17T06:38:21.0025024Z try: 2025-07-17T06:38:21.0025473Z rollout_state = get_rollout_state_from_issue( 2025-07-17T06:38:21.0026198Z args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:38:21.0026858Z ) 2025-07-17T06:38:21.0027079Z 2025-07-17T06:38:21.0027283Z username = get_potential_pr_author( 2025-07-17T06:38:21.0027854Z args.github_token, 2025-07-17T06:38:21.0028354Z args.github_repo, 2025-07-17T06:38:21.0028845Z args.github_actor, 2025-07-17T06:38:21.0029353Z args.github_ref_type, 2025-07-17T06:38:21.0029871Z args.github_branch, 2025-07-17T06:38:21.0030363Z ) 2025-07-17T06:38:21.0030588Z 2025-07-17T06:38:21.0030864Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:38:21.0031339Z 2025-07-17T06:38:21.0031553Z runner_label_prefix = get_runner_prefix( 2025-07-17T06:38:21.0032121Z rollout_state, 2025-07-17T06:38:21.0032613Z (args.github_issue_owner, username), 2025-07-17T06:38:21.0033181Z args.github_branch, 2025-07-17T06:38:21.0033694Z args.eligible_experiments, 2025-07-17T06:38:21.0034244Z args.opt_out_experiments, 2025-07-17T06:38:21.0034859Z is_canary, 2025-07-17T06:38:21.0035296Z ) 2025-07-17T06:38:21.0035516Z 2025-07-17T06:38:21.0035701Z except Exception as e: 2025-07-17T06:38:21.0036170Z log.error( 2025-07-17T06:38:21.0036847Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:38:21.0037785Z ) 2025-07-17T06:38:21.0038011Z 2025-07-17T06:38:21.0038343Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:38:21.0038864Z 2025-07-17T06:38:21.0038874Z 2025-07-17T06:38:21.0039048Z if __name__ == "__main__": 2025-07-17T06:38:21.0039510Z main() 2025-07-17T06:38:21.0039733Z 2025-07-17T06:38:21.0135083Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:38:21.0136005Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:38:21.0164873Z shell: /usr/bin/bash -e {0} 2025-07-17T06:38:21.0165459Z env: 2025-07-17T06:38:21.0166118Z GITHUB_TOKEN: *** 2025-07-17T06:38:21.0166540Z ISSUE_NUMBER: 5132 2025-07-17T06:38:21.0166992Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:38:21.0167512Z ISSUE_OWNER: 2025-07-17T06:38:21.0167917Z CHECK_EXPERIMENTS: 2025-07-17T06:38:21.0168347Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:38:21.0168792Z PR_NUMBER: 2025-07-17T06:38:21.0169180Z ##[endgroup] 2025-07-17T06:38:22.1004197Z Defaulting to user installation because normal site-packages is not writeable 2025-07-17T06:38:22.6679225Z Collecting urllib3==1.26.18 2025-07-17T06:38:22.7069811Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-07-17T06:38:22.7272212Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.3 MB/s eta 0:00:00 2025-07-17T06:38:22.7519429Z Collecting PyGithub==2.3.0 2025-07-17T06:38:22.7607631Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-07-17T06:38:22.8054898Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-07-17T06:38:22.8091879Z 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:38:22.8137352Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-07-17T06:38:22.8154815Z 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:38:22.8169576Z 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:38:22.8428664Z Collecting Deprecated (from PyGithub==2.3.0) 2025-07-17T06:38:22.8467748Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-07-17T06:38:22.8703275Z 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:38:22.9826074Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:38:22.9864454Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-07-17T06:38:23.0917722Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-07-17T06:38:23.0958152Z 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:38:23.1152341Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:38:23.1188796Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-07-17T06:38:23.1426517Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-07-17T06:38:23.1537391Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 14.5 MB/s eta 0:00:00 2025-07-17T06:38:23.1582876Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-07-17T06:38:23.1714867Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 30.7 MB/s eta 0:00:00 2025-07-17T06:38:23.1753000Z 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:38:23.1969177Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 43.2 MB/s eta 0:00:00 2025-07-17T06:38:23.2005565Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-07-17T06:38:23.2061969Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-07-17T06:38:23.2183434Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 45.9 MB/s eta 0:00:00 2025-07-17T06:38:23.2220611Z 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:38:23.2262829Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 32.1 MB/s eta 0:00:00 2025-07-17T06:38:23.2297896Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-07-17T06:38:23.2345585Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 35.7 MB/s eta 0:00:00 2025-07-17T06:38:23.5560812Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-07-17T06:38:24.0969244Z 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:38:24.1954961Z ##[group]Run curr_branch="main" 2025-07-17T06:38:24.1955272Z curr_branch="main" 2025-07-17T06:38:24.1955519Z curr_ref_type="branch" 2025-07-17T06:38:24.1955787Z echo "Current branch is '$curr_branch'" 2025-07-17T06:38:24.1956051Z  2025-07-17T06:38:24.1956233Z python3 runner_determinator.py \ 2025-07-17T06:38:24.1956510Z  --github-token "$GITHUB_TOKEN" \ 2025-07-17T06:38:24.1956770Z  --github-issue "$ISSUE_NUMBER" \ 2025-07-17T06:38:24.1957020Z  --github-branch "$curr_branch" \ 2025-07-17T06:38:24.1957274Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-07-17T06:38:24.1957564Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-07-17T06:38:24.1957846Z  --github-ref-type "$curr_ref_type" \ 2025-07-17T06:38:24.1958111Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-07-17T06:38:24.1958406Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-07-17T06:38:24.1958766Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-07-17T06:38:24.1959052Z  --pr-number "${PR_NUMBER}" 2025-07-17T06:38:24.1988104Z shell: /usr/bin/bash -e {0} 2025-07-17T06:38:24.1988325Z env: 2025-07-17T06:38:24.1988874Z GITHUB_TOKEN: *** 2025-07-17T06:38:24.1989065Z ISSUE_NUMBER: 5132 2025-07-17T06:38:24.1989280Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:38:24.1989506Z ISSUE_OWNER: 2025-07-17T06:38:24.1989680Z CHECK_EXPERIMENTS: 2025-07-17T06:38:24.1989860Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:38:24.1990084Z PR_NUMBER: 2025-07-17T06:38:24.1990244Z ##[endgroup] 2025-07-17T06:38:24.2036432Z Current branch is 'main' 2025-07-17T06:38:25.8988731Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-07-17T06:38:25.8990103Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-07-17T06:38:25.8990980Z INFO : Setting output: label-type='' 2025-07-17T06:38:25.9337958Z Evaluate and set job outputs 2025-07-17T06:38:25.9345268Z Cleaning up orphan processes