2025-07-17T06:33:20.1580063Z Current runner version: '2.326.0' 2025-07-17T06:33:20.1601675Z ##[group]Runner Image Provisioner 2025-07-17T06:33:20.1602455Z Hosted Compute Agent 2025-07-17T06:33:20.1602973Z Version: 20250711.363 2025-07-17T06:33:20.1603623Z Commit: 6785254374ce925a23743850c1cb91912ce5c14c 2025-07-17T06:33:20.1604304Z Build Date: 2025-07-11T20:04:25Z 2025-07-17T06:33:20.1604856Z ##[endgroup] 2025-07-17T06:33:20.1605473Z ##[group]Operating System 2025-07-17T06:33:20.1606038Z Ubuntu 2025-07-17T06:33:20.1606470Z 24.04.2 2025-07-17T06:33:20.1607364Z LTS 2025-07-17T06:33:20.1607874Z ##[endgroup] 2025-07-17T06:33:20.1608367Z ##[group]Runner Image 2025-07-17T06:33:20.1608966Z Image: ubuntu-24.04 2025-07-17T06:33:20.1609503Z Version: 20250710.1.0 2025-07-17T06:33:20.1610448Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250710.1/images/ubuntu/Ubuntu2404-Readme.md 2025-07-17T06:33:20.1611999Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250710.1 2025-07-17T06:33:20.1612978Z ##[endgroup] 2025-07-17T06:33:20.1613981Z ##[group]GITHUB_TOKEN Permissions 2025-07-17T06:33:20.1615906Z Contents: read 2025-07-17T06:33:20.1616520Z Metadata: read 2025-07-17T06:33:20.1617324Z ##[endgroup] 2025-07-17T06:33:20.1619522Z Secret source: Actions 2025-07-17T06:33:20.1620155Z Prepare workflow directory 2025-07-17T06:33:20.2169516Z Prepare all required actions 2025-07-17T06:33:20.2222356Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (a38f433be2e94a64b095a44ba39879d02d0c2316) 2025-07-17T06:33:20.2227219Z ##[group] Inputs 2025-07-17T06:33:20.2227819Z check_experiments: 2025-07-17T06:33:20.2228312Z opt_out_experiments: 2025-07-17T06:33:20.2228993Z triggering_actor: pytorchmergebot 2025-07-17T06:33:20.2229629Z issue_owner: 2025-07-17T06:33:20.2230199Z curr_branch: main 2025-07-17T06:33:20.2230832Z curr_ref_type: branch 2025-07-17T06:33:20.2231445Z issue_number: 5132 2025-07-17T06:33:20.2231944Z ##[endgroup] 2025-07-17T06:33:20.2232658Z Complete job name: before-test / get-label-type / runner-determinator 2025-07-17T06:33:20.9379641Z ##[group]Run cat < runner_determinator.py 2025-07-17T06:33:20.9382111Z cat < runner_determinator.py 2025-07-17T06:33:20.9382815Z # flake8: noqa: G004 2025-07-17T06:33:20.9383357Z  2025-07-17T06:33:20.9384240Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:33:20.9385341Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:33:20.9386296Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:33:20.9387292Z  2025-07-17T06:33:20.9387745Z """ 2025-07-17T06:33:20.9388487Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:33:20.9389609Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:33:20.9390822Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:33:20.9391901Z of which runners should be used to run which job. 2025-07-17T06:33:20.9392605Z  2025-07-17T06:33:20.9393347Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:33:20.9394393Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:33:20.9395520Z settings are considered to be empty with only the second part, the user 2025-07-17T06:33:20.9396406Z list, defined. 2025-07-17T06:33:20.9397002Z  2025-07-17T06:33:20.9397810Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:33:20.9398868Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:33:20.9399866Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:33:20.9400717Z  2025-07-17T06:33:20.9401699Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:33:20.9402730Z The user list is also a comma separated list of additional features or 2025-07-17T06:33:20.9403696Z experiments which the user could be opted in to. 2025-07-17T06:33:20.9404397Z  2025-07-17T06:33:20.9404882Z The user list has the following rules: 2025-07-17T06:33:20.9405592Z  2025-07-17T06:33:20.9406306Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:33:20.9407555Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:33:20.9408700Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:33:20.9409374Z  2025-07-17T06:33:20.9409862Z Example config: 2025-07-17T06:33:20.9410588Z  # A list of experiments that can be opted into. 2025-07-17T06:33:20.9411385Z  # This defines the behavior they'll induce when opted into. 2025-07-17T06:33:20.9412179Z  # Expected syntax is: 2025-07-17T06:33:20.9413002Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:33:20.9414165Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:33:20.9415056Z  2025-07-17T06:33:20.9415541Z  experiments: 2025-07-17T06:33:20.9416141Z  lf: 2025-07-17T06:33:20.9416641Z  rollout_percent: 25 2025-07-17T06:33:20.9417661Z  all_branches: false 2025-07-17T06:33:20.9418291Z  default: true 2025-07-17T06:33:20.9418870Z  --- 2025-07-17T06:33:20.9419319Z  2025-07-17T06:33:20.9419917Z  # Opt-ins: 2025-07-17T06:33:20.9420694Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:33:20.9493080Z  # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:33:20.9494184Z  # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:33:20.9494955Z  # Experiments should be from the above list. 2025-07-17T06:33:20.9495556Z  2025-07-17T06:33:20.9495970Z  @User1,-lf,split_build 2025-07-17T06:33:20.9496515Z  @User2,lf 2025-07-17T06:33:20.9497266Z  @User3,split_build 2025-07-17T06:33:20.9497799Z """ 2025-07-17T06:33:20.9498192Z  2025-07-17T06:33:20.9498590Z import json 2025-07-17T06:33:20.9499030Z import logging 2025-07-17T06:33:20.9499473Z import os 2025-07-17T06:33:20.9499890Z import random 2025-07-17T06:33:20.9500333Z import re 2025-07-17T06:33:20.9500749Z import sys 2025-07-17T06:33:20.9501217Z from argparse import ArgumentParser 2025-07-17T06:33:20.9501886Z from collections.abc import Iterable 2025-07-17T06:33:20.9502476Z from functools import cache 2025-07-17T06:33:20.9503026Z from logging import LogRecord 2025-07-17T06:33:20.9503591Z from typing import Any, NamedTuple 2025-07-17T06:33:20.9504215Z from urllib.request import Request, urlopen 2025-07-17T06:33:20.9504798Z  2025-07-17T06:33:20.9505183Z import yaml 2025-07-17T06:33:20.9505643Z from github import Auth, Github 2025-07-17T06:33:20.9506196Z from github.Issue import Issue 2025-07-17T06:33:20.9506721Z  2025-07-17T06:33:20.9507325Z  2025-07-17T06:33:20.9507792Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:33:20.9508560Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:33:20.9509523Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:33:20.9510282Z  2025-07-17T06:33:20.9510951Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:33:20.9511592Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:33:20.9512183Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:33:20.9512838Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:33:20.9513396Z  2025-07-17T06:33:20.9513832Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:33:20.9514374Z  2025-07-17T06:33:20.9514781Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:33:20.9515312Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:33:20.9515804Z  2025-07-17T06:33:20.9516170Z  2025-07-17T06:33:20.9516573Z class Experiment(NamedTuple): 2025-07-17T06:33:20.9517244Z  rollout_perc: float = ( 2025-07-17T06:33:20.9517992Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:33:20.9518723Z  ) 2025-07-17T06:33:20.9519147Z  all_branches: bool = ( 2025-07-17T06:33:20.9519887Z  False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:33:20.9520614Z  ) 2025-07-17T06:33:20.9521023Z  default: bool = ( 2025-07-17T06:33:20.9521678Z  True # If True, the experiment is enabled by default for all queries 2025-07-17T06:33:20.9522362Z  ) 2025-07-17T06:33:20.9522746Z  2025-07-17T06:33:20.9523143Z  # Add more fields as needed 2025-07-17T06:33:20.9523660Z  2025-07-17T06:33:20.9524023Z  2025-07-17T06:33:20.9524423Z class Settings(NamedTuple): 2025-07-17T06:33:20.9524928Z  """ 2025-07-17T06:33:20.9525448Z  Settings for the experiments that can be opted into. 2025-07-17T06:33:20.9526077Z  """ 2025-07-17T06:33:20.9526473Z  2025-07-17T06:33:20.9527006Z  experiments: dict[str, Experiment] = {} 2025-07-17T06:33:20.9527582Z  2025-07-17T06:33:20.9528084Z  2025-07-17T06:33:20.9528544Z class ColorFormatter(logging.Formatter): 2025-07-17T06:33:20.9529245Z  """Color codes the log messages based on the log level""" 2025-07-17T06:33:20.9529874Z  2025-07-17T06:33:20.9530248Z  COLORS = { 2025-07-17T06:33:20.9530719Z  "WARNING": "\033[33m", # Yellow 2025-07-17T06:33:20.9531275Z  "ERROR": "\033[31m", # Red 2025-07-17T06:33:20.9531828Z  "CRITICAL": "\033[31m", # Red 2025-07-17T06:33:20.9532384Z  "INFO": "\033[0m", # Reset 2025-07-17T06:33:20.9532937Z  "DEBUG": "\033[0m", # Reset 2025-07-17T06:33:20.9533460Z  } 2025-07-17T06:33:20.9533852Z  2025-07-17T06:33:20.9534306Z  def format(self, record: LogRecord) -> str: 2025-07-17T06:33:20.9535118Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:33:20.9535986Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:33:20.9536631Z  return super().format(record) 2025-07-17T06:33:20.9537484Z  2025-07-17T06:33:20.9537861Z  2025-07-17T06:33:20.9538289Z handler = logging.StreamHandler() 2025-07-17T06:33:20.9539095Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:33:20.9539860Z  2025-07-17T06:33:20.9540365Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:33:20.9541014Z log.addHandler(handler) 2025-07-17T06:33:20.9541529Z log.setLevel(logging.INFO) 2025-07-17T06:33:20.9542019Z  2025-07-17T06:33:20.9542381Z  2025-07-17T06:33:20.9542875Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:33:20.9543499Z  """ 2025-07-17T06:33:20.9544080Z  Defines outputs of the github action that invokes this script 2025-07-17T06:33:20.9544906Z  """ 2025-07-17T06:33:20.9545333Z  if not GITHUB_OUTPUT: 2025-07-17T06:33:20.9546488Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:33:20.9547812Z  log.warning( 2025-07-17T06:33:20.9548773Z  "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:20.9549748Z  ) 2025-07-17T06:33:20.9550247Z  print(f"::set-output name={key}::{value}") 2025-07-17T06:33:20.9550829Z  return 2025-07-17T06:33:20.9551266Z  2025-07-17T06:33:20.9551696Z  with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:33:20.9552335Z  log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:33:20.9552961Z  f.write(f"{key}={value}\n") 2025-07-17T06:33:20.9553492Z  2025-07-17T06:33:20.9553870Z  2025-07-17T06:33:20.9554421Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:33:20.9555143Z  return frozenset( 2025-07-17T06:33:20.9555846Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:33:20.9556578Z  ) 2025-07-17T06:33:20.9557071Z  2025-07-17T06:33:20.9557454Z  2025-07-17T06:33:20.9557860Z def parse_args() -> Any: 2025-07-17T06:33:20.9558513Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:33:20.9559463Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:33:20.9560281Z  parser.add_argument( 2025-07-17T06:33:20.9560814Z  "--github-issue-repo", 2025-07-17T06:33:20.9561349Z  type=str, 2025-07-17T06:33:20.9561833Z  required=False, 2025-07-17T06:33:20.9562490Z  default="pytorch/test-infra", 2025-07-17T06:33:20.9563101Z  help="GitHub repo to get the issue", 2025-07-17T06:33:20.9563654Z  ) 2025-07-17T06:33:20.9564077Z  parser.add_argument( 2025-07-17T06:33:20.9564590Z  "--github-repo", 2025-07-17T06:33:20.9565084Z  type=str, 2025-07-17T06:33:20.9565565Z  required=True, 2025-07-17T06:33:20.9566111Z  help="GitHub repo where CI is running", 2025-07-17T06:33:20.9566677Z  ) 2025-07-17T06:33:20.9567210Z  parser.add_argument( 2025-07-17T06:33:20.9567913Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:33:20.9568626Z  ) 2025-07-17T06:33:20.9569040Z  parser.add_argument( 2025-07-17T06:33:20.9569751Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:33:20.9570475Z  ) 2025-07-17T06:33:20.9570890Z  parser.add_argument( 2025-07-17T06:33:20.9571611Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:33:20.9572342Z  ) 2025-07-17T06:33:20.9572767Z  parser.add_argument( 2025-07-17T06:33:20.9573554Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:33:20.9574318Z  ) 2025-07-17T06:33:20.9574750Z  parser.add_argument( 2025-07-17T06:33:20.9575279Z  "--github-ref-type", 2025-07-17T06:33:20.9575801Z  type=str, 2025-07-17T06:33:20.9576277Z  required=True, 2025-07-17T06:33:20.9577054Z  help="Current GitHub ref type, branch or tag", 2025-07-17T06:33:20.9577792Z  ) 2025-07-17T06:33:20.9578214Z  parser.add_argument( 2025-07-17T06:33:20.9578957Z  "--eligible-experiments", 2025-07-17T06:33:20.9579555Z  type=_str_comma_separated_to_set, 2025-07-17T06:33:20.9580137Z  required=False, 2025-07-17T06:33:20.9580633Z  default="", 2025-07-17T06:33:20.9581569Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:33:20.9582550Z  ) 2025-07-17T06:33:20.9582966Z  parser.add_argument( 2025-07-17T06:33:20.9583494Z  "--opt-out-experiments", 2025-07-17T06:33:20.9584069Z  type=_str_comma_separated_to_set, 2025-07-17T06:33:20.9584633Z  required=False, 2025-07-17T06:33:20.9585125Z  default="", 2025-07-17T06:33:20.9585593Z  help=( 2025-07-17T06:33:20.9586343Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:33:20.9587898Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:33:20.9588797Z  ), 2025-07-17T06:33:20.9589214Z  ) 2025-07-17T06:33:20.9589640Z  parser.add_argument( 2025-07-17T06:33:20.9590152Z  "--pr-number", 2025-07-17T06:33:20.9590642Z  type=str, 2025-07-17T06:33:20.9591116Z  required=False, 2025-07-17T06:33:20.9591606Z  default="", 2025-07-17T06:33:20.9592170Z  help="the optional PR number where this is run", 2025-07-17T06:33:20.9592770Z  ) 2025-07-17T06:33:20.9593162Z  2025-07-17T06:33:20.9593569Z  return parser.parse_args() 2025-07-17T06:33:20.9594091Z  2025-07-17T06:33:20.9594458Z  2025-07-17T06:33:20.9595104Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:33:20.9596066Z  auth = Auth.Token(github_token) 2025-07-17T06:33:20.9596664Z  return Github(auth=auth) 2025-07-17T06:33:20.9597321Z  2025-07-17T06:33:20.9597685Z  2025-07-17T06:33:20.9598414Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:33:20.9599278Z  repo = gh.get_repo(repo) 2025-07-17T06:33:20.9599862Z  return repo.get_issue(number=issue_num) 2025-07-17T06:33:20.9600437Z  2025-07-17T06:33:20.9600806Z  2025-07-17T06:33:20.9601205Z def get_potential_pr_author( 2025-07-17T06:33:20.9601932Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:33:20.9602659Z ) -> str: 2025-07-17T06:33:20.9603244Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:33:20.9604125Z  # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:33:20.9604959Z  # embedded in the tag name: ciflow// 2025-07-17T06:33:20.9605576Z  2025-07-17T06:33:20.9605998Z  gh = get_gh_client(github_token) 2025-07-17T06:33:20.9606526Z  2025-07-17T06:33:20.9607146Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:33:20.9607844Z  split_tag = ref_name.split("/") 2025-07-17T06:33:20.9608409Z  if ( 2025-07-17T06:33:20.9608868Z  len(split_tag) == 3 2025-07-17T06:33:20.9609424Z  and split_tag[0] == "ciflow" 2025-07-17T06:33:20.9610022Z  and split_tag[2].isnumeric() 2025-07-17T06:33:20.9610569Z  ): 2025-07-17T06:33:20.9611028Z  pr_number = split_tag[2] 2025-07-17T06:33:20.9611569Z  try: 2025-07-17T06:33:20.9612073Z  repository = gh.get_repo(repo) 2025-07-17T06:33:20.9612889Z  pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:33:20.9613554Z  except Exception as e: 2025-07-17T06:33:20.9614142Z  raise Exception( # noqa: TRY002 2025-07-17T06:33:20.9614868Z  f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:33:20.9615565Z  ) from e 2025-07-17T06:33:20.9616189Z  return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:33:20.9617214Z  # In all other cases, return the original input username 2025-07-17T06:33:20.9617883Z  return username 2025-07-17T06:33:20.9618348Z  2025-07-17T06:33:20.9618723Z  2025-07-17T06:33:20.9619184Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:33:20.9619765Z  """ 2025-07-17T06:33:20.9620490Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:33:20.9621331Z  """ 2025-07-17T06:33:20.9621945Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:33:20.9622649Z  2025-07-17T06:33:20.9623021Z  2025-07-17T06:33:20.9623434Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:33:20.9623977Z  try: 2025-07-17T06:33:20.9624421Z  data = yaml.safe_load(yaml_text) 2025-07-17T06:33:20.9624977Z  return data 2025-07-17T06:33:20.9625461Z  except yaml.YAMLError: 2025-07-17T06:33:20.9626029Z  log.exception("Error loading YAML") 2025-07-17T06:33:20.9626587Z  raise 2025-07-17T06:33:20.9627111Z  2025-07-17T06:33:20.9627478Z  2025-07-17T06:33:20.9628138Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:33:20.9628932Z  """ 2025-07-17T06:33:20.9629762Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:33:20.9630566Z  2025-07-17T06:33:20.9631150Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:20.9631986Z  and the text below is the list of opted in users. 2025-07-17T06:33:20.9632589Z  2025-07-17T06:33:20.9633204Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:33:20.9633956Z  """ 2025-07-17T06:33:20.9634455Z  rollout_state_parts = rollout_state.split("---") 2025-07-17T06:33:20.9635115Z  if len(rollout_state_parts) >= 2: 2025-07-17T06:33:20.9635796Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:33:20.9636426Z  else: 2025-07-17T06:33:20.9637042Z  return "", rollout_state 2025-07-17T06:33:20.9637737Z  2025-07-17T06:33:20.9638111Z  2025-07-17T06:33:20.9638548Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:33:20.9639107Z  """ 2025-07-17T06:33:20.9639706Z  Dictionary of users with a list of features they have opted into 2025-07-17T06:33:20.9640395Z  """ 2025-07-17T06:33:20.9640786Z  2025-07-17T06:33:20.9641144Z  2025-07-17T06:33:20.9641721Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:33:20.9642420Z  """ 2025-07-17T06:33:20.9643203Z  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:20.9644081Z  2025-07-17T06:33:20.9644948Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:33:20.9646006Z  - Example line: "@User1,lf,split_build" 2025-07-17T06:33:20.9646980Z  - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:33:20.9647665Z  2025-07-17T06:33:20.9648021Z  2025-07-17T06:33:20.9648381Z  """ 2025-07-17T06:33:20.9648792Z  optins = UserOptins() 2025-07-17T06:33:20.9649360Z  for user in user_optin_text.split("\n"): 2025-07-17T06:33:20.9649973Z  user = user.strip("\r\n\t -") 2025-07-17T06:33:20.9650590Z  if not user or not user.startswith("@"): 2025-07-17T06:33:20.9651197Z  # Not a valid user. Skip 2025-07-17T06:33:20.9651728Z  continue 2025-07-17T06:33:20.9652180Z  2025-07-17T06:33:20.9652550Z  if user: 2025-07-17T06:33:20.9653092Z  usr_name = user.split(",")[0].strip("@") 2025-07-17T06:33:20.9653844Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:33:20.9654528Z  2025-07-17T06:33:20.9654913Z  return optins 2025-07-17T06:33:20.9655359Z  2025-07-17T06:33:20.9655714Z  2025-07-17T06:33:20.9656244Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:33:20.9657111Z  """ 2025-07-17T06:33:20.9657668Z  Check if the experiment name is valid. 2025-07-17T06:33:20.9658243Z  A valid name: 2025-07-17T06:33:20.9658973Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:33:20.9659968Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:33:20.9660757Z  - Cannot contain spaces 2025-07-17T06:33:20.9661278Z  """ 2025-07-17T06:33:20.9661670Z  2025-07-17T06:33:20.9662161Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:33:20.9662934Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:33:20.9663713Z  2025-07-17T06:33:20.9664086Z  if valid: 2025-07-17T06:33:20.9664524Z  return True 2025-07-17T06:33:20.9664964Z  2025-07-17T06:33:20.9665331Z  log.error( 2025-07-17T06:33:20.9667314Z  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:20.9668966Z  ) 2025-07-17T06:33:20.9669372Z  return False 2025-07-17T06:33:20.9669810Z  2025-07-17T06:33:20.9670170Z  2025-07-17T06:33:20.9670721Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:33:20.9671402Z  """ 2025-07-17T06:33:20.9672056Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:33:20.9672835Z  """ 2025-07-17T06:33:20.9673225Z  try: 2025-07-17T06:33:20.9673672Z  if settings_text: 2025-07-17T06:33:20.9674476Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:33:20.9675316Z  # for easy reading 2025-07-17T06:33:20.9676188Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:33:20.9677251Z  # the backtick character in shell commands. 2025-07-17T06:33:20.9677920Z  backtick = chr(96) # backtick character 2025-07-17T06:33:20.9678669Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:33:20.9679394Z  settings = load_yaml(settings_text) 2025-07-17T06:33:20.9679942Z  2025-07-17T06:33:20.9680574Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:33:20.9681517Z  experiments = {} 2025-07-17T06:33:20.9682012Z  2025-07-17T06:33:20.9682607Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:33:20.9683431Z  if not is_valid_experiment_name(exp_name): 2025-07-17T06:33:20.9684597Z  # 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:20.9685688Z  continue 2025-07-17T06:33:20.9686179Z  2025-07-17T06:33:20.9686584Z  valid_settings = {} 2025-07-17T06:33:20.9687268Z  for setting in exp_settings: 2025-07-17T06:33:20.9687886Z  if setting not in Experiment._fields: 2025-07-17T06:33:20.9688504Z  log.warning( 2025-07-17T06:33:20.9689292Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:33:20.9690046Z  ) 2025-07-17T06:33:20.9690533Z  else: 2025-07-17T06:33:20.9691118Z  valid_settings[setting] = exp_settings[setting] 2025-07-17T06:33:20.9691731Z  2025-07-17T06:33:20.9692234Z  experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:33:20.9692922Z  return Settings(experiments) 2025-07-17T06:33:20.9693452Z  2025-07-17T06:33:20.9693840Z  except Exception: 2025-07-17T06:33:20.9694393Z  log.exception("Failed to parse settings") 2025-07-17T06:33:20.9694970Z  2025-07-17T06:33:20.9695358Z  return Settings() 2025-07-17T06:33:20.9695819Z  2025-07-17T06:33:20.9696191Z  2025-07-17T06:33:20.9696842Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:33:20.9697598Z  """ 2025-07-17T06:33:20.9698100Z  Parse settings, if any, from the rollout state. 2025-07-17T06:33:20.9698694Z  2025-07-17T06:33:20.9699271Z  If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:20.9700090Z  and the text below is the list of opted in users. 2025-07-17T06:33:20.9700684Z  2025-07-17T06:33:20.9701315Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:33:20.9702078Z  """ 2025-07-17T06:33:20.9702693Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:20.9703513Z  return parse_settings_from_text(settings_text) 2025-07-17T06:33:20.9704096Z  2025-07-17T06:33:20.9704449Z  2025-07-17T06:33:20.9704947Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:33:20.9705551Z  """ 2025-07-17T06:33:20.9705996Z  Parse users from the rollout state. 2025-07-17T06:33:20.9706531Z  2025-07-17T06:33:20.9706986Z  """ 2025-07-17T06:33:20.9707586Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:20.9708393Z  return parse_user_opt_in_from_text(users_text) 2025-07-17T06:33:20.9708979Z  2025-07-17T06:33:20.9709332Z  2025-07-17T06:33:20.9709998Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:20.9710784Z  """ 2025-07-17T06:33:20.9711261Z  Check if a user is opted into an experiment 2025-07-17T06:33:20.9711826Z  """ 2025-07-17T06:33:20.9712342Z  return experiment_name in user_optins.get(user, []) 2025-07-17T06:33:20.9712952Z  2025-07-17T06:33:20.9713447Z  2025-07-17T06:33:20.9714126Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:20.9714914Z  """ 2025-07-17T06:33:20.9715432Z  Check if a user explicitly opted out of an experiment 2025-07-17T06:33:20.9716047Z  """ 2025-07-17T06:33:20.9716614Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:33:20.9717466Z  experiment_optout = "-" + experiment_name 2025-07-17T06:33:20.9718173Z  if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:33:20.9718828Z  return False 2025-07-17T06:33:20.9719284Z  2025-07-17T06:33:20.9719787Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:33:20.9720420Z  log.warning( 2025-07-17T06:33:20.9721310Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:33:20.9722228Z  ) 2025-07-17T06:33:20.9722623Z  2025-07-17T06:33:20.9723001Z  return True 2025-07-17T06:33:20.9723426Z  2025-07-17T06:33:20.9723790Z  2025-07-17T06:33:20.9724174Z def get_runner_prefix( 2025-07-17T06:33:20.9724694Z  rollout_state: str, 2025-07-17T06:33:20.9725236Z  workflow_requestors: Iterable[str], 2025-07-17T06:33:20.9725806Z  branch: str, 2025-07-17T06:33:20.9726376Z  eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:20.9727219Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:20.9727850Z  is_canary: bool = False, 2025-07-17T06:33:20.9728346Z ) -> str: 2025-07-17T06:33:20.9728851Z  settings = parse_settings(rollout_state) 2025-07-17T06:33:20.9729479Z  user_optins = parse_users(rollout_state) 2025-07-17T06:33:20.9730046Z  2025-07-17T06:33:20.9730553Z  fleet_prefix = "" 2025-07-17T06:33:20.9731054Z  prefixes = [] 2025-07-17T06:33:20.9731767Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:33:20.9732783Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:33:20.9733541Z  log.info( 2025-07-17T06:33:20.9734301Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:33:20.9735095Z  ) 2025-07-17T06:33:20.9735532Z  continue 2025-07-17T06:33:20.9735993Z  2025-07-17T06:33:20.9736399Z  if opt_out_experiments: 2025-07-17T06:33:20.9737106Z  if experiment_name in opt_out_experiments: 2025-07-17T06:33:20.9737826Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:33:20.9738463Z  log.info( 2025-07-17T06:33:20.9739463Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:33:20.9740480Z  ) 2025-07-17T06:33:20.9740966Z  continue 2025-07-17T06:33:20.9741445Z  2025-07-17T06:33:20.9741848Z  if eligible_experiments: 2025-07-17T06:33:20.9742477Z  if experiment_name not in eligible_experiments: 2025-07-17T06:33:20.9743155Z  exp_list = ", ".join(eligible_experiments) 2025-07-17T06:33:20.9743739Z  log.info( 2025-07-17T06:33:20.9744593Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:33:20.9745467Z  ) 2025-07-17T06:33:20.9746061Z  continue 2025-07-17T06:33:20.9746611Z  elif not experiment_settings.default: 2025-07-17T06:33:20.9747288Z  log.info( 2025-07-17T06:33:20.9748021Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:33:20.9748800Z  ) 2025-07-17T06:33:20.9749228Z  continue 2025-07-17T06:33:20.9749673Z  2025-07-17T06:33:20.9750184Z  # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:33:20.9750843Z  opted_out_users = [ 2025-07-17T06:33:20.9751363Z  requestor 2025-07-17T06:33:20.9751892Z  for requestor in workflow_requestors 2025-07-17T06:33:20.9752620Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:33:20.9753290Z  ] 2025-07-17T06:33:20.9753687Z  2025-07-17T06:33:20.9754087Z  if opted_out_users: 2025-07-17T06:33:20.9754606Z  log.info( 2025-07-17T06:33:20.9755296Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:33:20.9756029Z  ) 2025-07-17T06:33:20.9756464Z  continue 2025-07-17T06:33:20.9756999Z  2025-07-17T06:33:20.9757500Z  # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:33:20.9758153Z  opted_in_users = [ 2025-07-17T06:33:20.9758674Z  requestor 2025-07-17T06:33:20.9759210Z  for requestor in workflow_requestors 2025-07-17T06:33:20.9759939Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:33:20.9760604Z  ] 2025-07-17T06:33:20.9761005Z  2025-07-17T06:33:20.9761391Z  enabled = False 2025-07-17T06:33:20.9761899Z  if opted_in_users: 2025-07-17T06:33:20.9762519Z  log.info( 2025-07-17T06:33:20.9763229Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:33:20.9763948Z  ) 2025-07-17T06:33:20.9764391Z  enabled = True 2025-07-17T06:33:20.9764870Z  2025-07-17T06:33:20.9765314Z  elif experiment_settings.rollout_perc: 2025-07-17T06:33:20.9766205Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:33:20.9767306Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:33:20.9768019Z  log.info( 2025-07-17T06:33:20.9768966Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:33:20.9769934Z  ) 2025-07-17T06:33:20.9770411Z  enabled = True 2025-07-17T06:33:20.9770914Z  2025-07-17T06:33:20.9771297Z  if enabled: 2025-07-17T06:33:20.9771794Z  label = experiment_name 2025-07-17T06:33:20.9772410Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:33:20.9773293Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:33:20.9774261Z  # - If it's enabled, then we always list it's prefix first 2025-07-17T06:33:20.9775082Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:33:20.9775784Z  if is_canary: 2025-07-17T06:33:20.9776342Z  label += CANARY_FLEET_SUFFIX 2025-07-17T06:33:20.9777018Z  fleet_prefix = label 2025-07-17T06:33:20.9777562Z  else: 2025-07-17T06:33:20.9778193Z  prefixes.append(label) 2025-07-17T06:33:20.9778744Z  2025-07-17T06:33:20.9779136Z  if len(prefixes) > 1: 2025-07-17T06:33:20.9779644Z  log.error( 2025-07-17T06:33:20.9780766Z  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:20.9781929Z  ) 2025-07-17T06:33:20.9782375Z  prefixes = prefixes[:1] 2025-07-17T06:33:20.9782884Z  2025-07-17T06:33:20.9783285Z  # Fleet always comes first 2025-07-17T06:33:20.9783813Z  if fleet_prefix: 2025-07-17T06:33:20.9784324Z  prefixes.insert(0, fleet_prefix) 2025-07-17T06:33:20.9784865Z  2025-07-17T06:33:20.9785347Z  return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:33:20.9785948Z  2025-07-17T06:33:20.9786316Z  2025-07-17T06:33:20.9787100Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:33:20.9787915Z  """ 2025-07-17T06:33:20.9788558Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:33:20.9789312Z  2025-07-17T06:33:20.9789929Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:33:20.9790682Z  """ 2025-07-17T06:33:20.9791123Z  gh = get_gh_client(github_token) 2025-07-17T06:33:20.9791729Z  issue = get_issue(gh, repo, issue_num) 2025-07-17T06:33:20.9792428Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:33:20.9793061Z  2025-07-17T06:33:20.9793429Z  2025-07-17T06:33:20.9794073Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:33:20.9795007Z  for _ in range(num_retries): 2025-07-17T06:33:20.9795541Z  try: 2025-07-17T06:33:20.9796026Z  req = Request(url=url, headers=headers) 2025-07-17T06:33:20.9796736Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:33:20.9797546Z  return json.loads(content) 2025-07-17T06:33:20.9798121Z  except Exception as e: 2025-07-17T06:33:20.9798726Z  log.warning(f"Could not download {url}: {e}") 2025-07-17T06:33:20.9799313Z  2025-07-17T06:33:20.9799925Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:33:20.9800687Z  return {} 2025-07-17T06:33:20.9801105Z  2025-07-17T06:33:20.9801462Z  2025-07-17T06:33:20.9801816Z @cache 2025-07-17T06:33:20.9802508Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:33:20.9803314Z  """ 2025-07-17T06:33:20.9803758Z  Dynamically get PR information 2025-07-17T06:33:20.9804289Z  """ 2025-07-17T06:33:20.9804846Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:33:20.9805513Z  headers = { 2025-07-17T06:33:20.9806041Z  "Accept": "application/vnd.github.v3+json", 2025-07-17T06:33:20.9806704Z  "Authorization": f"token {github_token}", 2025-07-17T06:33:20.9807367Z  } 2025-07-17T06:33:20.9807846Z  json_response: dict[str, Any] = download_json( 2025-07-17T06:33:20.9808504Z  url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:33:20.9809089Z  headers=headers, 2025-07-17T06:33:20.9809568Z  ) 2025-07-17T06:33:20.9809944Z  2025-07-17T06:33:20.9810338Z  if not json_response: 2025-07-17T06:33:20.9810985Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:33:20.9811790Z  return {} 2025-07-17T06:33:20.9812235Z  2025-07-17T06:33:20.9812618Z  return json_response 2025-07-17T06:33:20.9813106Z  2025-07-17T06:33:20.9813459Z  2025-07-17T06:33:20.9814080Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:33:20.9814847Z  """ 2025-07-17T06:33:20.9815437Z  Dynamically get the latest list of labels from the pull request 2025-07-17T06:33:20.9816128Z  """ 2025-07-17T06:33:20.9816660Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:33:20.9817425Z  return { 2025-07-17T06:33:20.9818066Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:33:20.9818803Z  } 2025-07-17T06:33:20.9819196Z  2025-07-17T06:33:20.9819573Z  2025-07-17T06:33:20.9819959Z def main() -> None: 2025-07-17T06:33:20.9820443Z  args = parse_args() 2025-07-17T06:33:20.9820916Z  2025-07-17T06:33:20.9821370Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:33:20.9821942Z  2025-07-17T06:33:20.9822342Z  # Check if the PR is opt-out 2025-07-17T06:33:20.9822889Z  if args.pr_number: 2025-07-17T06:33:20.9823620Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:33:20.9824432Z  if OPT_OUT_LABEL in labels: 2025-07-17T06:33:20.9824981Z  log.info( 2025-07-17T06:33:20.9825747Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:33:20.9826557Z  ) 2025-07-17T06:33:20.9827281Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:20.9828026Z  sys.exit() 2025-07-17T06:33:20.9828611Z  2025-07-17T06:33:20.9828996Z  try: 2025-07-17T06:33:20.9829495Z  rollout_state = get_rollout_state_from_issue( 2025-07-17T06:33:20.9830275Z  args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:33:20.9830954Z  ) 2025-07-17T06:33:20.9831351Z  2025-07-17T06:33:20.9831784Z  username = get_potential_pr_author( 2025-07-17T06:33:20.9832366Z  args.github_token, 2025-07-17T06:33:20.9832904Z  args.github_repo, 2025-07-17T06:33:20.9833437Z  args.github_actor, 2025-07-17T06:33:20.9833990Z  args.github_ref_type, 2025-07-17T06:33:20.9834538Z  args.github_branch, 2025-07-17T06:33:20.9835046Z  ) 2025-07-17T06:33:20.9835450Z  2025-07-17T06:33:20.9835968Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:33:20.9836618Z  2025-07-17T06:33:20.9837188Z  runner_label_prefix = get_runner_prefix( 2025-07-17T06:33:20.9837785Z  rollout_state, 2025-07-17T06:33:20.9838341Z  (args.github_issue_owner, username), 2025-07-17T06:33:20.9838936Z  args.github_branch, 2025-07-17T06:33:20.9839498Z  args.eligible_experiments, 2025-07-17T06:33:20.9840084Z  args.opt_out_experiments, 2025-07-17T06:33:20.9840636Z  is_canary, 2025-07-17T06:33:20.9841099Z  ) 2025-07-17T06:33:20.9841502Z  2025-07-17T06:33:20.9841890Z  except Exception as e: 2025-07-17T06:33:20.9842403Z  log.error( 2025-07-17T06:33:20.9843162Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:33:20.9844092Z  ) 2025-07-17T06:33:20.9844502Z  2025-07-17T06:33:20.9845065Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:20.9845765Z  2025-07-17T06:33:20.9846127Z  2025-07-17T06:33:20.9846517Z if __name__ == "__main__": 2025-07-17T06:33:20.9847116Z  main() 2025-07-17T06:33:20.9847529Z  2025-07-17T06:33:20.9847891Z EOF 2025-07-17T06:33:20.9848278Z  2025-07-17T06:33:20.9848681Z cat runner_determinator.py 2025-07-17T06:33:21.0064713Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:21.0065542Z env: 2025-07-17T06:33:21.0066241Z GITHUB_TOKEN: *** 2025-07-17T06:33:21.0066683Z ISSUE_NUMBER: 5132 2025-07-17T06:33:21.0067377Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:21.0067927Z ISSUE_OWNER: 2025-07-17T06:33:21.0068356Z CHECK_EXPERIMENTS: 2025-07-17T06:33:21.0068804Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:21.0069275Z PR_NUMBER: 2025-07-17T06:33:21.0069694Z ##[endgroup] 2025-07-17T06:33:21.0269541Z # flake8: noqa: G004 2025-07-17T06:33:21.0269897Z 2025-07-17T06:33:21.0270338Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-07-17T06:33:21.0271298Z # must be kept in sync. You can do it easily by running the following command: 2025-07-17T06:33:21.0272106Z # python .github/scripts/update_runner_determinator.py 2025-07-17T06:33:21.0272551Z 2025-07-17T06:33:21.0272724Z """ 2025-07-17T06:33:21.0273327Z This runner determinator is used to determine which set of runners to run a 2025-07-17T06:33:21.0274224Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-07-17T06:33:21.0275161Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-07-17T06:33:21.0275988Z of which runners should be used to run which job. 2025-07-17T06:33:21.0276401Z 2025-07-17T06:33:21.0276793Z The configuration has two parts, the settings and a list of opted-in users, 2025-07-17T06:33:21.0278264Z separated by a line containing "---". If the line is not present, the 2025-07-17T06:33:21.0279194Z settings are considered to be empty with only the second part, the user 2025-07-17T06:33:21.0279918Z list, defined. 2025-07-17T06:33:21.0280152Z 2025-07-17T06:33:21.0280528Z The first part is a YAML block that defines the rollout settings. This can be 2025-07-17T06:33:21.0281477Z used to define any settings that are needed to determine which runners to use. 2025-07-17T06:33:21.0282312Z It's fields are defined by the RolloutSettings class below. 2025-07-17T06:33:21.0282763Z 2025-07-17T06:33:21.0283148Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-07-17T06:33:21.0284048Z The user list is also a comma separated list of additional features or 2025-07-17T06:33:21.0284803Z experiments which the user could be opted in to. 2025-07-17T06:33:21.0285206Z 2025-07-17T06:33:21.0285422Z The user list has the following rules: 2025-07-17T06:33:21.0285772Z 2025-07-17T06:33:21.0286105Z - Users are GitHub usernames, which must start with the @ prefix 2025-07-17T06:33:21.0287641Z - Each user is also a comma-separated list of features/experiments to enable 2025-07-17T06:33:21.0288474Z - A "#" prefix opts the user out of all experiments 2025-07-17T06:33:21.0288873Z 2025-07-17T06:33:21.0289053Z Example config: 2025-07-17T06:33:21.0289531Z # A list of experiments that can be opted into. 2025-07-17T06:33:21.0290216Z # This defines the behavior they'll induce when opted into. 2025-07-17T06:33:21.0290859Z # Expected syntax is: 2025-07-17T06:33:21.0291500Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-07-17T06:33:21.0292489Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-07-17T06:33:21.0293098Z 2025-07-17T06:33:21.0293290Z experiments: 2025-07-17T06:33:21.0293697Z lf: 2025-07-17T06:33:21.0294102Z rollout_percent: 25 2025-07-17T06:33:21.0294747Z all_branches: false 2025-07-17T06:33:21.0295218Z default: true 2025-07-17T06:33:21.0295641Z --- 2025-07-17T06:33:21.0295872Z 2025-07-17T06:33:21.0296061Z # Opt-ins: 2025-07-17T06:33:21.0296652Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-07-17T06:33:21.0297803Z # and specifying experiments to enable in a comma-separated list. 2025-07-17T06:33:21.0298611Z # To always opt out of an experiment, prefix it with a "-". 2025-07-17T06:33:21.0299289Z # Experiments should be from the above list. 2025-07-17T06:33:21.0299670Z 2025-07-17T06:33:21.0299877Z @User1,-lf,split_build 2025-07-17T06:33:21.0300330Z @User2,lf 2025-07-17T06:33:21.0300737Z @User3,split_build 2025-07-17T06:33:21.0301159Z """ 2025-07-17T06:33:21.0301369Z 2025-07-17T06:33:21.0301546Z import json 2025-07-17T06:33:21.0301936Z import logging 2025-07-17T06:33:21.0302342Z import os 2025-07-17T06:33:21.0302724Z import random 2025-07-17T06:33:21.0303126Z import re 2025-07-17T06:33:21.0303518Z import sys 2025-07-17T06:33:21.0303938Z from argparse import ArgumentParser 2025-07-17T06:33:21.0304484Z from collections.abc import Iterable 2025-07-17T06:33:21.0305024Z from functools import cache 2025-07-17T06:33:21.0305522Z from logging import LogRecord 2025-07-17T06:33:21.0306028Z from typing import Any, NamedTuple 2025-07-17T06:33:21.0306581Z from urllib.request import Request, urlopen 2025-07-17T06:33:21.0307152Z 2025-07-17T06:33:21.0307358Z import yaml 2025-07-17T06:33:21.0307825Z from github import Auth, Github 2025-07-17T06:33:21.0308353Z from github.Issue import Issue 2025-07-17T06:33:21.0308663Z 2025-07-17T06:33:21.0308670Z 2025-07-17T06:33:21.0308898Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-07-17T06:33:21.0309603Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-07-17T06:33:21.0310493Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-07-17T06:33:21.0311057Z 2025-07-17T06:33:21.0311299Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-07-17T06:33:21.0312033Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-07-17T06:33:21.0312588Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-07-17T06:33:21.0313147Z OPT_OUT_LABEL = "no-runner-experiments" 2025-07-17T06:33:21.0313505Z 2025-07-17T06:33:21.0313717Z SETTING_EXPERIMENTS = "experiments" 2025-07-17T06:33:21.0314050Z 2025-07-17T06:33:21.0314258Z LF_FLEET_EXPERIMENT = "lf" 2025-07-17T06:33:21.0314738Z CANARY_FLEET_SUFFIX = ".c" 2025-07-17T06:33:21.0315032Z 2025-07-17T06:33:21.0315039Z 2025-07-17T06:33:21.0315246Z class Experiment(NamedTuple): 2025-07-17T06:33:21.0315744Z rollout_perc: float = ( 2025-07-17T06:33:21.0316402Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-07-17T06:33:21.0317401Z ) 2025-07-17T06:33:21.0317803Z all_branches: bool = ( 2025-07-17T06:33:21.0318467Z False # If True, the experiment is also enabled on the exception branches 2025-07-17T06:33:21.0319156Z ) 2025-07-17T06:33:21.0319539Z default: bool = ( 2025-07-17T06:33:21.0320123Z True # If True, the experiment is enabled by default for all queries 2025-07-17T06:33:21.0320780Z ) 2025-07-17T06:33:21.0320978Z 2025-07-17T06:33:21.0321175Z # Add more fields as needed 2025-07-17T06:33:21.0321485Z 2025-07-17T06:33:21.0321492Z 2025-07-17T06:33:21.0321694Z class Settings(NamedTuple): 2025-07-17T06:33:21.0322158Z """ 2025-07-17T06:33:21.0322626Z Settings for the experiments that can be opted into. 2025-07-17T06:33:21.0323218Z """ 2025-07-17T06:33:21.0323415Z 2025-07-17T06:33:21.0323642Z experiments: dict[str, Experiment] = {} 2025-07-17T06:33:21.0324014Z 2025-07-17T06:33:21.0324021Z 2025-07-17T06:33:21.0324248Z class ColorFormatter(logging.Formatter): 2025-07-17T06:33:21.0324886Z """Color codes the log messages based on the log level""" 2025-07-17T06:33:21.0325331Z 2025-07-17T06:33:21.0325507Z COLORS = { 2025-07-17T06:33:21.0325929Z "WARNING": "\033[33m", # Yellow 2025-07-17T06:33:21.0326603Z "ERROR": "\033[31m", # Red 2025-07-17T06:33:21.0327328Z "CRITICAL": "\033[31m", # Red 2025-07-17T06:33:21.0327850Z "INFO": "\033[0m", # Reset 2025-07-17T06:33:21.0328358Z "DEBUG": "\033[0m", # Reset 2025-07-17T06:33:21.0328843Z } 2025-07-17T06:33:21.0329049Z 2025-07-17T06:33:21.0329282Z def format(self, record: LogRecord) -> str: 2025-07-17T06:33:21.0330050Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-07-17T06:33:21.0330850Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-07-17T06:33:21.0331455Z return super().format(record) 2025-07-17T06:33:21.0331793Z 2025-07-17T06:33:21.0331800Z 2025-07-17T06:33:21.0332013Z handler = logging.StreamHandler() 2025-07-17T06:33:21.0332737Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-07-17T06:33:21.0333292Z 2025-07-17T06:33:21.0333549Z log = logging.getLogger(os.path.basename(__file__)) 2025-07-17T06:33:21.0334169Z log.addHandler(handler) 2025-07-17T06:33:21.0334627Z log.setLevel(logging.INFO) 2025-07-17T06:33:21.0334922Z 2025-07-17T06:33:21.0334930Z 2025-07-17T06:33:21.0335193Z def set_github_output(key: str, value: str) -> None: 2025-07-17T06:33:21.0335776Z """ 2025-07-17T06:33:21.0336319Z Defines outputs of the github action that invokes this script 2025-07-17T06:33:21.0337197Z """ 2025-07-17T06:33:21.0337609Z if not GITHUB_OUTPUT: 2025-07-17T06:33:21.0338698Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-07-17T06:33:21.0339852Z log.warning( 2025-07-17T06:33:21.0340712Z "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:21.0341674Z ) 2025-07-17T06:33:21.0352091Z print(f"::set-output name={key}::{value}") 2025-07-17T06:33:21.0352723Z return 2025-07-17T06:33:21.0352961Z 2025-07-17T06:33:21.0353381Z with open(GITHUB_OUTPUT, "a") as f: 2025-07-17T06:33:21.0354006Z log.info(f"Setting output: {key}='{value}'") 2025-07-17T06:33:21.0354598Z f.write(f"{key}={value}\n") 2025-07-17T06:33:21.0354949Z 2025-07-17T06:33:21.0354956Z 2025-07-17T06:33:21.0355274Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-07-17T06:33:21.0355928Z return frozenset( 2025-07-17T06:33:21.0356545Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-07-17T06:33:21.0357546Z ) 2025-07-17T06:33:21.0357755Z 2025-07-17T06:33:21.0357762Z 2025-07-17T06:33:21.0357959Z def parse_args() -> Any: 2025-07-17T06:33:21.0358547Z parser = ArgumentParser("Get dynamic rollout settings") 2025-07-17T06:33:21.0359421Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-07-17T06:33:21.0360224Z parser.add_argument( 2025-07-17T06:33:21.0360701Z "--github-issue-repo", 2025-07-17T06:33:21.0361184Z type=str, 2025-07-17T06:33:21.0361622Z required=False, 2025-07-17T06:33:21.0362089Z default="pytorch/test-infra", 2025-07-17T06:33:21.0362640Z help="GitHub repo to get the issue", 2025-07-17T06:33:21.0363164Z ) 2025-07-17T06:33:21.0363545Z parser.add_argument( 2025-07-17T06:33:21.0364007Z "--github-repo", 2025-07-17T06:33:21.0364457Z type=str, 2025-07-17T06:33:21.0364869Z required=True, 2025-07-17T06:33:21.0365348Z help="GitHub repo where CI is running", 2025-07-17T06:33:21.0365899Z ) 2025-07-17T06:33:21.0366284Z parser.add_argument( 2025-07-17T06:33:21.0367067Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-07-17T06:33:21.0367772Z ) 2025-07-17T06:33:21.0368157Z parser.add_argument( 2025-07-17T06:33:21.0368785Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-07-17T06:33:21.0369480Z ) 2025-07-17T06:33:21.0369870Z parser.add_argument( 2025-07-17T06:33:21.0370679Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-07-17T06:33:21.0371397Z ) 2025-07-17T06:33:21.0371794Z parser.add_argument( 2025-07-17T06:33:21.0372474Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-07-17T06:33:21.0373209Z ) 2025-07-17T06:33:21.0373602Z parser.add_argument( 2025-07-17T06:33:21.0374075Z "--github-ref-type", 2025-07-17T06:33:21.0374809Z type=str, 2025-07-17T06:33:21.0375232Z required=True, 2025-07-17T06:33:21.0423540Z help="Current GitHub ref type, branch or tag", 2025-07-17T06:33:21.0424675Z ) 2025-07-17T06:33:21.0425380Z parser.add_argument( 2025-07-17T06:33:21.0425887Z "--eligible-experiments", 2025-07-17T06:33:21.0426423Z type=_str_comma_separated_to_set, 2025-07-17T06:33:21.0427176Z required=False, 2025-07-17T06:33:21.0427635Z default="", 2025-07-17T06:33:21.0428508Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-07-17T06:33:21.0429458Z ) 2025-07-17T06:33:21.0429988Z parser.add_argument( 2025-07-17T06:33:21.0430473Z "--opt-out-experiments", 2025-07-17T06:33:21.0430999Z type=_str_comma_separated_to_set, 2025-07-17T06:33:21.0431529Z required=False, 2025-07-17T06:33:21.0431972Z default="", 2025-07-17T06:33:21.0432373Z help=( 2025-07-17T06:33:21.0433052Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-07-17T06:33:21.0434181Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-07-17T06:33:21.0435024Z ), 2025-07-17T06:33:21.0435397Z ) 2025-07-17T06:33:21.0435784Z parser.add_argument( 2025-07-17T06:33:21.0436279Z "--pr-number", 2025-07-17T06:33:21.0436707Z type=str, 2025-07-17T06:33:21.0437314Z required=False, 2025-07-17T06:33:21.0437759Z default="", 2025-07-17T06:33:21.0438435Z help="the optional PR number where this is run", 2025-07-17T06:33:21.0439017Z ) 2025-07-17T06:33:21.0439220Z 2025-07-17T06:33:21.0439420Z return parser.parse_args() 2025-07-17T06:33:21.0439726Z 2025-07-17T06:33:21.0439732Z 2025-07-17T06:33:21.0440183Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-07-17T06:33:21.0440946Z auth = Auth.Token(github_token) 2025-07-17T06:33:21.0441468Z return Github(auth=auth) 2025-07-17T06:33:21.0441762Z 2025-07-17T06:33:21.0441770Z 2025-07-17T06:33:21.0442224Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-07-17T06:33:21.0443030Z repo = gh.get_repo(repo) 2025-07-17T06:33:21.0443542Z return repo.get_issue(number=issue_num) 2025-07-17T06:33:21.0443905Z 2025-07-17T06:33:21.0443912Z 2025-07-17T06:33:21.0444115Z def get_potential_pr_author( 2025-07-17T06:33:21.0444779Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-07-17T06:33:21.0445462Z ) -> str: 2025-07-17T06:33:21.0445994Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-07-17T06:33:21.0446793Z # Fetch the actual username from the original PR. The PR number is 2025-07-17T06:33:21.0447797Z # embedded in the tag name: ciflow// 2025-07-17T06:33:21.0448218Z 2025-07-17T06:33:21.0448423Z gh = get_gh_client(github_token) 2025-07-17T06:33:21.0448764Z 2025-07-17T06:33:21.0449038Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-07-17T06:33:21.0449676Z split_tag = ref_name.split("/") 2025-07-17T06:33:21.0450190Z if ( 2025-07-17T06:33:21.0450596Z len(split_tag) == 3 2025-07-17T06:33:21.0451093Z and split_tag[0] == "ciflow" 2025-07-17T06:33:21.0451635Z and split_tag[2].isnumeric() 2025-07-17T06:33:21.0452138Z ): 2025-07-17T06:33:21.0452547Z pr_number = split_tag[2] 2025-07-17T06:33:21.0453191Z try: 2025-07-17T06:33:21.0453650Z repository = gh.get_repo(repo) 2025-07-17T06:33:21.0454279Z pull = repository.get_pull(number=int(pr_number)) 2025-07-17T06:33:21.0454919Z except Exception as e: 2025-07-17T06:33:21.0455455Z raise Exception( # noqa: TRY002 2025-07-17T06:33:21.0456123Z f"issue with pull request {pr_number} from repo {repository}" 2025-07-17T06:33:21.0456770Z ) from e 2025-07-17T06:33:21.0457520Z return pull.user.login # type: ignore[no-any-return] 2025-07-17T06:33:21.0458221Z # In all other cases, return the original input username 2025-07-17T06:33:21.0458819Z return username 2025-07-17T06:33:21.0459065Z 2025-07-17T06:33:21.0459071Z 2025-07-17T06:33:21.0459316Z def is_exception_branch(branch: str) -> bool: 2025-07-17T06:33:21.0459869Z """ 2025-07-17T06:33:21.0460517Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-07-17T06:33:21.0461303Z """ 2025-07-17T06:33:21.0461857Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-07-17T06:33:21.0462376Z 2025-07-17T06:33:21.0462383Z 2025-07-17T06:33:21.0462592Z def load_yaml(yaml_text: str) -> Any: 2025-07-17T06:33:21.0463096Z try: 2025-07-17T06:33:21.0463498Z data = yaml.safe_load(yaml_text) 2025-07-17T06:33:21.0464024Z return data 2025-07-17T06:33:21.0464454Z except yaml.YAMLError: 2025-07-17T06:33:21.0464949Z log.exception("Error loading YAML") 2025-07-17T06:33:21.0465464Z raise 2025-07-17T06:33:21.0465678Z 2025-07-17T06:33:21.0465689Z 2025-07-17T06:33:21.0466124Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-07-17T06:33:21.0467042Z """ 2025-07-17T06:33:21.0467721Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-07-17T06:33:21.0468315Z 2025-07-17T06:33:21.0468822Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:21.0469599Z and the text below is the list of opted in users. 2025-07-17T06:33:21.0469996Z 2025-07-17T06:33:21.0470381Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-07-17T06:33:21.0471087Z """ 2025-07-17T06:33:21.0471547Z rollout_state_parts = rollout_state.split("---") 2025-07-17T06:33:21.0472165Z if len(rollout_state_parts) >= 2: 2025-07-17T06:33:21.0472783Z return rollout_state_parts[0], rollout_state_parts[1] 2025-07-17T06:33:21.0473382Z else: 2025-07-17T06:33:21.0473776Z return "", rollout_state 2025-07-17T06:33:21.0474083Z 2025-07-17T06:33:21.0474090Z 2025-07-17T06:33:21.0474309Z class UserOptins(dict[str, list[str]]): 2025-07-17T06:33:21.0474828Z """ 2025-07-17T06:33:21.0475354Z Dictionary of users with a list of features they have opted into 2025-07-17T06:33:21.0476005Z """ 2025-07-17T06:33:21.0476220Z 2025-07-17T06:33:21.0476227Z 2025-07-17T06:33:21.0476580Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-07-17T06:33:21.0477441Z """ 2025-07-17T06:33:21.0478152Z 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:21.0478834Z 2025-07-17T06:33:21.0479462Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-07-17T06:33:21.0480460Z - Example line: "@User1,lf,split_build" 2025-07-17T06:33:21.0481154Z - A "#" prefix indicates the user is opted out of all experiments 2025-07-17T06:33:21.0481629Z 2025-07-17T06:33:21.0481636Z 2025-07-17T06:33:21.0481812Z """ 2025-07-17T06:33:21.0482200Z optins = UserOptins() 2025-07-17T06:33:21.0482693Z for user in user_optin_text.split("\n"): 2025-07-17T06:33:21.0483265Z user = user.strip("\r\n\t -") 2025-07-17T06:33:21.0483826Z if not user or not user.startswith("@"): 2025-07-17T06:33:21.0484553Z # Not a valid user. Skip 2025-07-17T06:33:21.0485057Z continue 2025-07-17T06:33:21.0485301Z 2025-07-17T06:33:21.0485476Z if user: 2025-07-17T06:33:21.0485926Z usr_name = user.split(",")[0].strip("@") 2025-07-17T06:33:21.0486619Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-07-17T06:33:21.0487341Z 2025-07-17T06:33:21.0487529Z return optins 2025-07-17T06:33:21.0487782Z 2025-07-17T06:33:21.0487789Z 2025-07-17T06:33:21.0488092Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-07-17T06:33:21.0488706Z """ 2025-07-17T06:33:21.0489128Z Check if the experiment name is valid. 2025-07-17T06:33:21.0489658Z A valid name: 2025-07-17T06:33:21.0490298Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-07-17T06:33:21.0491259Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-07-17T06:33:21.0492005Z - Cannot contain spaces 2025-07-17T06:33:21.0492484Z """ 2025-07-17T06:33:21.0492683Z 2025-07-17T06:33:21.0492949Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-07-17T06:33:21.0493660Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-07-17T06:33:21.0494099Z 2025-07-17T06:33:21.0494278Z if valid: 2025-07-17T06:33:21.0494674Z return True 2025-07-17T06:33:21.0494915Z 2025-07-17T06:33:21.0495098Z log.error( 2025-07-17T06:33:21.0496550Z 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:21.0498338Z ) 2025-07-17T06:33:21.0498713Z return False 2025-07-17T06:33:21.0498949Z 2025-07-17T06:33:21.0498957Z 2025-07-17T06:33:21.0499268Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-07-17T06:33:21.0499907Z """ 2025-07-17T06:33:21.0500641Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-07-17T06:33:21.0501388Z """ 2025-07-17T06:33:21.0501749Z try: 2025-07-17T06:33:21.0502139Z if settings_text: 2025-07-17T06:33:21.0502871Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-07-17T06:33:21.0503673Z # for easy reading 2025-07-17T06:33:21.0504462Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-07-17T06:33:21.0505360Z # the backtick character in shell commands. 2025-07-17T06:33:21.0505971Z backtick = chr(96) # backtick character 2025-07-17T06:33:21.0506638Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-07-17T06:33:21.0507605Z settings = load_yaml(settings_text) 2025-07-17T06:33:21.0507982Z 2025-07-17T06:33:21.0508403Z # For now we just load experiments. We can expand this if/when we add more settings 2025-07-17T06:33:21.0509189Z experiments = {} 2025-07-17T06:33:21.0509485Z 2025-07-17T06:33:21.0509877Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-07-17T06:33:21.0510656Z if not is_valid_experiment_name(exp_name): 2025-07-17T06:33:21.0511779Z # 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:21.0512835Z continue 2025-07-17T06:33:21.0513126Z 2025-07-17T06:33:21.0513324Z valid_settings = {} 2025-07-17T06:33:21.0513847Z for setting in exp_settings: 2025-07-17T06:33:21.0514428Z if setting not in Experiment._fields: 2025-07-17T06:33:21.0515003Z log.warning( 2025-07-17T06:33:21.0515706Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-07-17T06:33:21.0516595Z ) 2025-07-17T06:33:21.0517218Z else: 2025-07-17T06:33:21.0517743Z valid_settings[setting] = exp_settings[setting] 2025-07-17T06:33:21.0518169Z 2025-07-17T06:33:21.0518460Z experiments[exp_name] = Experiment(**valid_settings) 2025-07-17T06:33:21.0519106Z return Settings(experiments) 2025-07-17T06:33:21.0519458Z 2025-07-17T06:33:21.0519652Z except Exception: 2025-07-17T06:33:21.0520136Z log.exception("Failed to parse settings") 2025-07-17T06:33:21.0520524Z 2025-07-17T06:33:21.0520715Z return Settings() 2025-07-17T06:33:21.0520970Z 2025-07-17T06:33:21.0520977Z 2025-07-17T06:33:21.0521239Z def parse_settings(rollout_state: str) -> Settings: 2025-07-17T06:33:21.0521827Z """ 2025-07-17T06:33:21.0522269Z Parse settings, if any, from the rollout state. 2025-07-17T06:33:21.0522676Z 2025-07-17T06:33:21.0523036Z If the issue body contains "---" then the text above that is the settings 2025-07-17T06:33:21.0523823Z and the text below is the list of opted in users. 2025-07-17T06:33:21.0524225Z 2025-07-17T06:33:21.0524636Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-07-17T06:33:21.0525388Z """ 2025-07-17T06:33:21.0525942Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:21.0526714Z return parse_settings_from_text(settings_text) 2025-07-17T06:33:21.0527345Z 2025-07-17T06:33:21.0527353Z 2025-07-17T06:33:21.0527620Z def parse_users(rollout_state: str) -> UserOptins: 2025-07-17T06:33:21.0528189Z """ 2025-07-17T06:33:21.0528601Z Parse users from the rollout state. 2025-07-17T06:33:21.0528950Z 2025-07-17T06:33:21.0529122Z """ 2025-07-17T06:33:21.0529663Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-07-17T06:33:21.0530425Z return parse_user_opt_in_from_text(users_text) 2025-07-17T06:33:21.0530840Z 2025-07-17T06:33:21.0530847Z 2025-07-17T06:33:21.0531433Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:21.0532217Z """ 2025-07-17T06:33:21.0532638Z Check if a user is opted into an experiment 2025-07-17T06:33:21.0533189Z """ 2025-07-17T06:33:21.0533645Z return experiment_name in user_optins.get(user, []) 2025-07-17T06:33:21.0534071Z 2025-07-17T06:33:21.0534078Z 2025-07-17T06:33:21.0534505Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-07-17T06:33:21.0535266Z """ 2025-07-17T06:33:21.0535737Z Check if a user explicitly opted out of an experiment 2025-07-17T06:33:21.0536364Z """ 2025-07-17T06:33:21.0537039Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-07-17T06:33:21.0537751Z experiment_optout = "-" + experiment_name 2025-07-17T06:33:21.0538394Z if experiment_optout not in user_optins.get(user, []): 2025-07-17T06:33:21.0539012Z return False 2025-07-17T06:33:21.0539279Z 2025-07-17T06:33:21.0539566Z if is_user_opted_in(user, user_optins, experiment_name): 2025-07-17T06:33:21.0540180Z log.warning( 2025-07-17T06:33:21.0541002Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-07-17T06:33:21.0541898Z ) 2025-07-17T06:33:21.0542110Z 2025-07-17T06:33:21.0542292Z return True 2025-07-17T06:33:21.0542523Z 2025-07-17T06:33:21.0542531Z 2025-07-17T06:33:21.0542720Z def get_runner_prefix( 2025-07-17T06:33:21.0543170Z rollout_state: str, 2025-07-17T06:33:21.0543640Z workflow_requestors: Iterable[str], 2025-07-17T06:33:21.0544178Z branch: str, 2025-07-17T06:33:21.0544673Z eligible_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:21.0545344Z opt_out_experiments: frozenset[str] = frozenset(), 2025-07-17T06:33:21.0545938Z is_canary: bool = False, 2025-07-17T06:33:21.0546411Z ) -> str: 2025-07-17T06:33:21.0547199Z settings = parse_settings(rollout_state) 2025-07-17T06:33:21.0547817Z user_optins = parse_users(rollout_state) 2025-07-17T06:33:21.0548188Z 2025-07-17T06:33:21.0548383Z fleet_prefix = "" 2025-07-17T06:33:21.0548813Z prefixes = [] 2025-07-17T06:33:21.0549453Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-07-17T06:33:21.0550386Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-07-17T06:33:21.0551115Z log.info( 2025-07-17T06:33:21.0551792Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-07-17T06:33:21.0552554Z ) 2025-07-17T06:33:21.0552949Z continue 2025-07-17T06:33:21.0553198Z 2025-07-17T06:33:21.0553397Z if opt_out_experiments: 2025-07-17T06:33:21.0553937Z if experiment_name in opt_out_experiments: 2025-07-17T06:33:21.0554574Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-07-17T06:33:21.0555183Z log.info( 2025-07-17T06:33:21.0556112Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-07-17T06:33:21.0557281Z ) 2025-07-17T06:33:21.0557697Z continue 2025-07-17T06:33:21.0557964Z 2025-07-17T06:33:21.0558162Z if eligible_experiments: 2025-07-17T06:33:21.0558750Z if experiment_name not in eligible_experiments: 2025-07-17T06:33:21.0559382Z exp_list = ", ".join(eligible_experiments) 2025-07-17T06:33:21.0559948Z log.info( 2025-07-17T06:33:21.0560746Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-07-17T06:33:21.0561582Z ) 2025-07-17T06:33:21.0561995Z continue 2025-07-17T06:33:21.0562480Z elif not experiment_settings.default: 2025-07-17T06:33:21.0563030Z log.info( 2025-07-17T06:33:21.0563824Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-07-17T06:33:21.0564577Z ) 2025-07-17T06:33:21.0564962Z continue 2025-07-17T06:33:21.0565215Z 2025-07-17T06:33:21.0565499Z # Is any workflow_requestor opted out to this experiment? 2025-07-17T06:33:21.0566123Z opted_out_users = [ 2025-07-17T06:33:21.0566579Z requestor 2025-07-17T06:33:21.0567270Z for requestor in workflow_requestors 2025-07-17T06:33:21.0567968Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-07-17T06:33:21.0568608Z ] 2025-07-17T06:33:21.0568817Z 2025-07-17T06:33:21.0569007Z if opted_out_users: 2025-07-17T06:33:21.0569470Z log.info( 2025-07-17T06:33:21.0570085Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-07-17T06:33:21.0570800Z ) 2025-07-17T06:33:21.0571194Z continue 2025-07-17T06:33:21.0571447Z 2025-07-17T06:33:21.0571751Z # Is any workflow_requestor opted in to this experiment? 2025-07-17T06:33:21.0572382Z opted_in_users = [ 2025-07-17T06:33:21.0572834Z requestor 2025-07-17T06:33:21.0573297Z for requestor in workflow_requestors 2025-07-17T06:33:21.0573961Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-07-17T06:33:21.0574588Z ] 2025-07-17T06:33:21.0574795Z 2025-07-17T06:33:21.0574981Z enabled = False 2025-07-17T06:33:21.0575424Z if opted_in_users: 2025-07-17T06:33:21.0575875Z log.info( 2025-07-17T06:33:21.0576474Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-07-17T06:33:21.0577338Z ) 2025-07-17T06:33:21.0577743Z enabled = True 2025-07-17T06:33:21.0578029Z 2025-07-17T06:33:21.0578258Z elif experiment_settings.rollout_perc: 2025-07-17T06:33:21.0579094Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-07-17T06:33:21.0580184Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-07-17T06:33:21.0580845Z log.info( 2025-07-17T06:33:21.0581714Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-07-17T06:33:21.0582643Z ) 2025-07-17T06:33:21.0583061Z enabled = True 2025-07-17T06:33:21.0583358Z 2025-07-17T06:33:21.0583534Z if enabled: 2025-07-17T06:33:21.0583962Z label = experiment_name 2025-07-17T06:33:21.0584529Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-07-17T06:33:21.0585364Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-07-17T06:33:21.0586248Z # - If it's enabled, then we always list it's prefix first 2025-07-17T06:33:21.0587192Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-07-17T06:33:21.0587884Z if is_canary: 2025-07-17T06:33:21.0588417Z label += CANARY_FLEET_SUFFIX 2025-07-17T06:33:21.0588975Z fleet_prefix = label 2025-07-17T06:33:21.0589482Z else: 2025-07-17T06:33:21.0589918Z prefixes.append(label) 2025-07-17T06:33:21.0590269Z 2025-07-17T06:33:21.0590460Z if len(prefixes) > 1: 2025-07-17T06:33:21.0590915Z log.error( 2025-07-17T06:33:21.0591949Z 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:21.0593078Z ) 2025-07-17T06:33:21.0593475Z prefixes = prefixes[:1] 2025-07-17T06:33:21.0593784Z 2025-07-17T06:33:21.0593983Z # Fleet always comes first 2025-07-17T06:33:21.0594464Z if fleet_prefix: 2025-07-17T06:33:21.0594931Z prefixes.insert(0, fleet_prefix) 2025-07-17T06:33:21.0595292Z 2025-07-17T06:33:21.0595684Z return ".".join(prefixes) + "." if prefixes else "" 2025-07-17T06:33:21.0596106Z 2025-07-17T06:33:21.0596113Z 2025-07-17T06:33:21.0596563Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-07-17T06:33:21.0597553Z """ 2025-07-17T06:33:21.0598147Z Gets the first comment of the issue, which contains the desired rollout state. 2025-07-17T06:33:21.0598707Z 2025-07-17T06:33:21.0599093Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-07-17T06:33:21.0599804Z """ 2025-07-17T06:33:21.0600212Z gh = get_gh_client(github_token) 2025-07-17T06:33:21.0600757Z issue = get_issue(gh, repo, issue_num) 2025-07-17T06:33:21.0601394Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-07-17T06:33:21.0601837Z 2025-07-17T06:33:21.0601845Z 2025-07-17T06:33:21.0602250Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-07-17T06:33:21.0603022Z for _ in range(num_retries): 2025-07-17T06:33:21.0603515Z try: 2025-07-17T06:33:21.0603951Z req = Request(url=url, headers=headers) 2025-07-17T06:33:21.0604616Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-07-17T06:33:21.0605265Z return json.loads(content) 2025-07-17T06:33:21.0605796Z except Exception as e: 2025-07-17T06:33:21.0606343Z log.warning(f"Could not download {url}: {e}") 2025-07-17T06:33:21.0606740Z 2025-07-17T06:33:21.0607341Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-07-17T06:33:21.0608078Z return {} 2025-07-17T06:33:21.0608305Z 2025-07-17T06:33:21.0608312Z 2025-07-17T06:33:21.0608479Z @cache 2025-07-17T06:33:21.0609112Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-07-17T06:33:21.0609886Z """ 2025-07-17T06:33:21.0610287Z Dynamically get PR information 2025-07-17T06:33:21.0610782Z """ 2025-07-17T06:33:21.0611433Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-07-17T06:33:21.0612070Z headers = { 2025-07-17T06:33:21.0612534Z "Accept": "application/vnd.github.v3+json", 2025-07-17T06:33:21.0613151Z "Authorization": f"token {github_token}", 2025-07-17T06:33:21.0613695Z } 2025-07-17T06:33:21.0614139Z json_response: dict[str, Any] = download_json( 2025-07-17T06:33:21.0614748Z url=f"{github_api}/issues/{pr_number}", 2025-07-17T06:33:21.0615308Z headers=headers, 2025-07-17T06:33:21.0615826Z ) 2025-07-17T06:33:21.0616032Z 2025-07-17T06:33:21.0616224Z if not json_response: 2025-07-17T06:33:21.0616797Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-07-17T06:33:21.0618096Z return {} 2025-07-17T06:33:21.0618358Z 2025-07-17T06:33:21.0618552Z return json_response 2025-07-17T06:33:21.0618828Z 2025-07-17T06:33:21.0618834Z 2025-07-17T06:33:21.0619238Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-07-17T06:33:21.0619990Z """ 2025-07-17T06:33:21.0620534Z Dynamically get the latest list of labels from the pull request 2025-07-17T06:33:21.0621199Z """ 2025-07-17T06:33:21.0621691Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-07-17T06:33:21.0622311Z return { 2025-07-17T06:33:21.0622906Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-07-17T06:33:21.0623618Z } 2025-07-17T06:33:21.0623828Z 2025-07-17T06:33:21.0623835Z 2025-07-17T06:33:21.0624022Z def main() -> None: 2025-07-17T06:33:21.0624453Z args = parse_args() 2025-07-17T06:33:21.0624728Z 2025-07-17T06:33:21.0624959Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-07-17T06:33:21.0625341Z 2025-07-17T06:33:21.0625547Z # Check if the PR is opt-out 2025-07-17T06:33:21.0626044Z if args.pr_number: 2025-07-17T06:33:21.0626706Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-07-17T06:33:21.0627871Z if OPT_OUT_LABEL in labels: 2025-07-17T06:33:21.0628403Z log.info( 2025-07-17T06:33:21.0629091Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-07-17T06:33:21.0629871Z ) 2025-07-17T06:33:21.0630433Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:21.0631114Z sys.exit() 2025-07-17T06:33:21.0631373Z 2025-07-17T06:33:21.0631552Z try: 2025-07-17T06:33:21.0631989Z rollout_state = get_rollout_state_from_issue( 2025-07-17T06:33:21.0632695Z args.github_token, args.github_issue_repo, args.github_issue 2025-07-17T06:33:21.0633343Z ) 2025-07-17T06:33:21.0633559Z 2025-07-17T06:33:21.0633777Z username = get_potential_pr_author( 2025-07-17T06:33:21.0634334Z args.github_token, 2025-07-17T06:33:21.0634817Z args.github_repo, 2025-07-17T06:33:21.0635304Z args.github_actor, 2025-07-17T06:33:21.0635794Z args.github_ref_type, 2025-07-17T06:33:21.0636346Z args.github_branch, 2025-07-17T06:33:21.0636819Z ) 2025-07-17T06:33:21.0637226Z 2025-07-17T06:33:21.0637524Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-07-17T06:33:21.0637977Z 2025-07-17T06:33:21.0638207Z runner_label_prefix = get_runner_prefix( 2025-07-17T06:33:21.0638779Z rollout_state, 2025-07-17T06:33:21.0639280Z (args.github_issue_owner, username), 2025-07-17T06:33:21.0639839Z args.github_branch, 2025-07-17T06:33:21.0640345Z args.eligible_experiments, 2025-07-17T06:33:21.0640892Z args.opt_out_experiments, 2025-07-17T06:33:21.0641409Z is_canary, 2025-07-17T06:33:21.0641827Z ) 2025-07-17T06:33:21.0642042Z 2025-07-17T06:33:21.0642233Z except Exception as e: 2025-07-17T06:33:21.0642689Z log.error( 2025-07-17T06:33:21.0643363Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-07-17T06:33:21.0644294Z ) 2025-07-17T06:33:21.0644506Z 2025-07-17T06:33:21.0644844Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-07-17T06:33:21.0645340Z 2025-07-17T06:33:21.0645348Z 2025-07-17T06:33:21.0645545Z if __name__ == "__main__": 2025-07-17T06:33:21.0645994Z main() 2025-07-17T06:33:21.0646207Z 2025-07-17T06:33:21.0736079Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:33:21.0737242Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-07-17T06:33:21.0765397Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:21.0765882Z env: 2025-07-17T06:33:21.0766487Z GITHUB_TOKEN: *** 2025-07-17T06:33:21.0767052Z ISSUE_NUMBER: 5132 2025-07-17T06:33:21.0767497Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:21.0768006Z ISSUE_OWNER: 2025-07-17T06:33:21.0768409Z CHECK_EXPERIMENTS: 2025-07-17T06:33:21.0768836Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:21.0769279Z PR_NUMBER: 2025-07-17T06:33:21.0769658Z ##[endgroup] 2025-07-17T06:33:22.5251987Z Defaulting to user installation because normal site-packages is not writeable 2025-07-17T06:33:23.2694918Z Collecting urllib3==1.26.18 2025-07-17T06:33:23.3027246Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-07-17T06:33:23.3244888Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.3 MB/s eta 0:00:00 2025-07-17T06:33:23.3438515Z Collecting PyGithub==2.3.0 2025-07-17T06:33:23.3526029Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-07-17T06:33:23.3945684Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-07-17T06:33:23.3968531Z 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:23.4027425Z 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:23.4043013Z 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:23.4057307Z 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:23.4318532Z Collecting Deprecated (from PyGithub==2.3.0) 2025-07-17T06:33:23.4340121Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-07-17T06:33:23.4561890Z 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:23.5620232Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:33:23.5643182Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-07-17T06:33:23.6637722Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-07-17T06:33:23.6666396Z 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:23.6854440Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-07-17T06:33:23.6888171Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-07-17T06:33:23.7103252Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-07-17T06:33:23.7211722Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 14.7 MB/s eta 0:00:00 2025-07-17T06:33:23.7235717Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-07-17T06:33:23.7363836Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 46.3 MB/s eta 0:00:00 2025-07-17T06:33:23.7394160Z 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:23.7573965Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 51.7 MB/s eta 0:00:00 2025-07-17T06:33:23.7601264Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-07-17T06:33:23.7654553Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-07-17T06:33:23.7732500Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 76.7 MB/s eta 0:00:00 2025-07-17T06:33:23.7754310Z 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:23.7793442Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 33.5 MB/s eta 0:00:00 2025-07-17T06:33:23.7818964Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-07-17T06:33:23.7861881Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 39.3 MB/s eta 0:00:00 2025-07-17T06:33:24.0916314Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-07-17T06:33:24.6176831Z 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:24.6873734Z ##[group]Run curr_branch="main" 2025-07-17T06:33:24.6874028Z curr_branch="main" 2025-07-17T06:33:24.6874278Z curr_ref_type="branch" 2025-07-17T06:33:24.6874535Z echo "Current branch is '$curr_branch'" 2025-07-17T06:33:24.6874784Z  2025-07-17T06:33:24.6874969Z python3 runner_determinator.py \ 2025-07-17T06:33:24.6875239Z  --github-token "$GITHUB_TOKEN" \ 2025-07-17T06:33:24.6875512Z  --github-issue "$ISSUE_NUMBER" \ 2025-07-17T06:33:24.6875768Z  --github-branch "$curr_branch" \ 2025-07-17T06:33:24.6876021Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-07-17T06:33:24.6876298Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-07-17T06:33:24.6876564Z  --github-ref-type "$curr_ref_type" \ 2025-07-17T06:33:24.6876828Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-07-17T06:33:24.6877296Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-07-17T06:33:24.6877660Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-07-17T06:33:24.6877944Z  --pr-number "${PR_NUMBER}" 2025-07-17T06:33:24.6906724Z shell: /usr/bin/bash -e {0} 2025-07-17T06:33:24.6907188Z env: 2025-07-17T06:33:24.6907728Z GITHUB_TOKEN: *** 2025-07-17T06:33:24.6907914Z ISSUE_NUMBER: 5132 2025-07-17T06:33:24.6908121Z TRIGGERING_ACTOR: pytorchmergebot 2025-07-17T06:33:24.6908346Z ISSUE_OWNER: 2025-07-17T06:33:24.6908527Z CHECK_EXPERIMENTS: 2025-07-17T06:33:24.6908724Z OPT_OUT_EXPERIMENTS: 2025-07-17T06:33:24.6908914Z PR_NUMBER: 2025-07-17T06:33:24.6909072Z ##[endgroup] 2025-07-17T06:33:24.6955860Z Current branch is 'main' 2025-07-17T06:33:26.7338245Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-07-17T06:33:26.7339192Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-07-17T06:33:26.7339698Z INFO : Setting output: label-type='' 2025-07-17T06:33:26.7643047Z Evaluate and set job outputs 2025-07-17T06:33:26.7650027Z Cleaning up orphan processes