2025-10-10T00:01:08.7199625Z Current runner version: '2.328.0' 2025-10-10T00:01:08.7225369Z ##[group]Runner Image Provisioner 2025-10-10T00:01:08.7226391Z Hosted Compute Agent 2025-10-10T00:01:08.7226972Z Version: 20250912.392 2025-10-10T00:01:08.7227585Z Commit: d921fda672a98b64f4f82364647e2f10b2267d0b 2025-10-10T00:01:08.7228251Z Build Date: 2025-09-12T15:23:14Z 2025-10-10T00:01:08.7228900Z ##[endgroup] 2025-10-10T00:01:08.7229401Z ##[group]Operating System 2025-10-10T00:01:08.7230615Z Ubuntu 2025-10-10T00:01:08.7231042Z 24.04.3 2025-10-10T00:01:08.7231598Z LTS 2025-10-10T00:01:08.7232012Z ##[endgroup] 2025-10-10T00:01:08.7232480Z ##[group]Runner Image 2025-10-10T00:01:08.7233151Z Image: ubuntu-24.04 2025-10-10T00:01:08.7233616Z Version: 20250929.60.1 2025-10-10T00:01:08.7234612Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250929.60/images/ubuntu/Ubuntu2404-Readme.md 2025-10-10T00:01:08.7236258Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250929.60 2025-10-10T00:01:08.7237225Z ##[endgroup] 2025-10-10T00:01:08.7239813Z ##[group]GITHUB_TOKEN Permissions 2025-10-10T00:01:08.7242328Z Actions: read 2025-10-10T00:01:08.7243345Z Attestations: read 2025-10-10T00:01:08.7243856Z Checks: read 2025-10-10T00:01:08.7244285Z Contents: read 2025-10-10T00:01:08.7244862Z Deployments: read 2025-10-10T00:01:08.7245358Z Discussions: read 2025-10-10T00:01:08.7245875Z Issues: read 2025-10-10T00:01:08.7246362Z Metadata: read 2025-10-10T00:01:08.7246846Z Models: read 2025-10-10T00:01:08.7247276Z Packages: read 2025-10-10T00:01:08.7247806Z Pages: read 2025-10-10T00:01:08.7248325Z PullRequests: read 2025-10-10T00:01:08.7248867Z RepositoryProjects: read 2025-10-10T00:01:08.7249716Z SecurityEvents: read 2025-10-10T00:01:08.7250342Z Statuses: read 2025-10-10T00:01:08.7250832Z ##[endgroup] 2025-10-10T00:01:08.7253015Z Secret source: Actions 2025-10-10T00:01:08.7254162Z Prepare workflow directory 2025-10-10T00:01:08.7785352Z Prepare all required actions 2025-10-10T00:01:08.7848410Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (344e6365a0068c2d2847fcec0c55dd53291d475e) 2025-10-10T00:01:08.7853520Z ##[group] Inputs 2025-10-10T00:01:08.7854252Z check_experiments: 2025-10-10T00:01:08.7854810Z opt_out_experiments: 2025-10-10T00:01:08.7855339Z triggering_actor: pytorchmergebot 2025-10-10T00:01:08.7856009Z issue_owner: 2025-10-10T00:01:08.7856504Z curr_branch: main 2025-10-10T00:01:08.7857022Z curr_ref_type: branch 2025-10-10T00:01:08.7857571Z issue_number: 5132 2025-10-10T00:01:08.7858109Z ##[endgroup] 2025-10-10T00:01:08.7858671Z Complete job name: get-label-type / runner-determinator 2025-10-10T00:01:08.8482834Z ##[group]Run cat < runner_determinator.py 2025-10-10T00:01:08.8485583Z cat < runner_determinator.py 2025-10-10T00:01:08.8486333Z # flake8: noqa: G004 2025-10-10T00:01:08.8487013Z  2025-10-10T00:01:08.8487815Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:01:08.8488978Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:01:08.8490365Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:01:08.8491180Z  2025-10-10T00:01:08.8491669Z """ 2025-10-10T00:01:08.8492512Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:01:08.8493620Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:01:08.8494829Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:01:08.8495955Z of which runners should be used to run which job. 2025-10-10T00:01:08.8496664Z  2025-10-10T00:01:08.8497509Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:01:08.8498615Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:01:08.8500308Z settings are considered to be empty with only the second part, the user 2025-10-10T00:01:08.8501298Z list, defined. 2025-10-10T00:01:08.8501859Z  2025-10-10T00:01:08.8502605Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:01:08.8503675Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:01:08.8504795Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:01:08.8505576Z  2025-10-10T00:01:08.8506403Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:01:08.8507506Z The user list is also a comma separated list of additional features or 2025-10-10T00:01:08.8508431Z experiments which the user could be opted in to. 2025-10-10T00:01:08.8509231Z  2025-10-10T00:01:08.8509950Z The user list has the following rules: 2025-10-10T00:01:08.8510610Z  2025-10-10T00:01:08.8511439Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:01:08.8512481Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:01:08.8513464Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:01:08.8514141Z  2025-10-10T00:01:08.8514752Z Example config: 2025-10-10T00:01:08.8515451Z  # A list of experiments that can be opted into. 2025-10-10T00:01:08.8516367Z  # This defines the behavior they'll induce when opted into. 2025-10-10T00:01:08.8517206Z  # Expected syntax is: 2025-10-10T00:01:08.8518045Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:01:08.8519276Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:01:08.8520486Z  2025-10-10T00:01:08.8521008Z  experiments: 2025-10-10T00:01:08.8521713Z  lf: 2025-10-10T00:01:08.8522289Z  rollout_percent: 25 2025-10-10T00:01:08.8522976Z  all_branches: false 2025-10-10T00:01:08.8523691Z  default: true 2025-10-10T00:01:08.8524349Z  --- 2025-10-10T00:01:08.8524876Z  2025-10-10T00:01:08.8525510Z  # Opt-ins: 2025-10-10T00:01:08.8526452Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:01:08.8527782Z  # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:01:08.8528821Z  # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:01:08.8529987Z  # Experiments should be from the above list. 2025-10-10T00:01:08.8530726Z  2025-10-10T00:01:08.8531356Z  @User1,-lf,split_build 2025-10-10T00:01:08.8531990Z  @User2,lf 2025-10-10T00:01:08.8532605Z  @User3,split_build 2025-10-10T00:01:08.8533274Z """ 2025-10-10T00:01:08.8533795Z  2025-10-10T00:01:08.8534366Z import json 2025-10-10T00:01:08.8534922Z import logging 2025-10-10T00:01:08.8535577Z import os 2025-10-10T00:01:08.8536122Z import random 2025-10-10T00:01:08.8536752Z import re 2025-10-10T00:01:08.8537307Z import sys 2025-10-10T00:01:08.8537969Z from argparse import ArgumentParser 2025-10-10T00:01:08.8538753Z from collections.abc import Iterable 2025-10-10T00:01:08.8539578Z from functools import cache 2025-10-10T00:01:08.8540300Z from logging import LogRecord 2025-10-10T00:01:08.8540966Z from typing import Any, NamedTuple 2025-10-10T00:01:08.8541801Z from urllib.request import Request, urlopen 2025-10-10T00:01:08.8542501Z  2025-10-10T00:01:08.8543049Z import yaml 2025-10-10T00:01:08.8543936Z from github import Auth, Github 2025-10-10T00:01:08.8544608Z from github.Issue import Issue 2025-10-10T00:01:08.8545319Z  2025-10-10T00:01:08.8545839Z  2025-10-10T00:01:08.8546492Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:01:08.8547351Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:01:08.8548493Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:01:08.8549385Z  2025-10-10T00:01:08.8550114Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:01:08.8550970Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:01:08.8551710Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:01:08.8552573Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:01:08.8553262Z  2025-10-10T00:01:08.8553844Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:01:08.8554589Z  2025-10-10T00:01:08.8555130Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:01:08.8555829Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:01:08.8556443Z  2025-10-10T00:01:08.8557071Z  2025-10-10T00:01:08.8557598Z class Experiment(NamedTuple): 2025-10-10T00:01:08.8558329Z  rollout_perc: float = ( 2025-10-10T00:01:08.8559178Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:01:08.8560286Z  ) 2025-10-10T00:01:08.8560937Z  all_branches: bool = ( 2025-10-10T00:01:08.8561828Z  False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:01:08.8563212Z  ) 2025-10-10T00:01:08.8563725Z  default: bool = ( 2025-10-10T00:01:08.8564610Z  True # If True, the experiment is enabled by default for all queries 2025-10-10T00:01:08.8565473Z  ) 2025-10-10T00:01:08.8565961Z  2025-10-10T00:01:08.8566596Z  # Add more fields as needed 2025-10-10T00:01:08.8567226Z  2025-10-10T00:01:08.8567731Z  2025-10-10T00:01:08.8568312Z class Settings(NamedTuple): 2025-10-10T00:01:08.8569006Z  """ 2025-10-10T00:01:08.8569959Z  Settings for the experiments that can be opted into. 2025-10-10T00:01:08.8570816Z  """ 2025-10-10T00:01:08.8571364Z  2025-10-10T00:01:08.8571919Z  experiments: dict[str, Experiment] = {} 2025-10-10T00:01:08.8572689Z  2025-10-10T00:01:08.8573373Z  2025-10-10T00:01:08.8573978Z class ColorFormatter(logging.Formatter): 2025-10-10T00:01:08.8574874Z  """Color codes the log messages based on the log level""" 2025-10-10T00:01:08.8575654Z  2025-10-10T00:01:08.8576161Z  COLORS = { 2025-10-10T00:01:08.8576817Z  "WARNING": "\033[33m", # Yellow 2025-10-10T00:01:08.8577570Z  "ERROR": "\033[31m", # Red 2025-10-10T00:01:08.8578217Z  "CRITICAL": "\033[31m", # Red 2025-10-10T00:01:08.8579013Z  "INFO": "\033[0m", # Reset 2025-10-10T00:01:08.8579817Z  "DEBUG": "\033[0m", # Reset 2025-10-10T00:01:08.8580497Z  } 2025-10-10T00:01:08.8581160Z  2025-10-10T00:01:08.8581744Z  def format(self, record: LogRecord) -> str: 2025-10-10T00:01:08.8582675Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:01:08.8653315Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:01:08.8654773Z  return super().format(record) 2025-10-10T00:01:08.8655699Z  2025-10-10T00:01:08.8656397Z  2025-10-10T00:01:08.8657117Z handler = logging.StreamHandler() 2025-10-10T00:01:08.8658478Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:01:08.8660363Z  2025-10-10T00:01:08.8661108Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:01:08.8661824Z log.addHandler(handler) 2025-10-10T00:01:08.8662394Z log.setLevel(logging.INFO) 2025-10-10T00:01:08.8662980Z  2025-10-10T00:01:08.8663402Z  2025-10-10T00:01:08.8663949Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:01:08.8664598Z  """ 2025-10-10T00:01:08.8665223Z  Defines outputs of the github action that invokes this script 2025-10-10T00:01:08.8665936Z  """ 2025-10-10T00:01:08.8666440Z  if not GITHUB_OUTPUT: 2025-10-10T00:01:08.8667615Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:01:08.8668783Z  log.warning( 2025-10-10T00:01:08.8669930Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:01:08.8670921Z  ) 2025-10-10T00:01:08.8671461Z  print(f"::set-output name={key}::{value}") 2025-10-10T00:01:08.8672096Z  return 2025-10-10T00:01:08.8672571Z  2025-10-10T00:01:08.8673044Z  with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:01:08.8673719Z  log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:01:08.8674407Z  f.write(f"{key}={value}\n") 2025-10-10T00:01:08.8674979Z  2025-10-10T00:01:08.8675395Z  2025-10-10T00:01:08.8675990Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:01:08.8676738Z  return frozenset( 2025-10-10T00:01:08.8677473Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:01:08.8678238Z  ) 2025-10-10T00:01:08.8678679Z  2025-10-10T00:01:08.8679104Z  2025-10-10T00:01:08.8679748Z def parse_args() -> Any: 2025-10-10T00:01:08.8680487Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:01:08.8681436Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:01:08.8682276Z  parser.add_argument( 2025-10-10T00:01:08.8682845Z  "--github-issue-repo", 2025-10-10T00:01:08.8683422Z  type=str, 2025-10-10T00:01:08.8683944Z  required=False, 2025-10-10T00:01:08.8684728Z  default="pytorch/test-infra", 2025-10-10T00:01:08.8685400Z  help="GitHub repo to get the issue", 2025-10-10T00:01:08.8686030Z  ) 2025-10-10T00:01:08.8686508Z  parser.add_argument( 2025-10-10T00:01:08.8687065Z  "--github-repo", 2025-10-10T00:01:08.8687593Z  type=str, 2025-10-10T00:01:08.8688105Z  required=True, 2025-10-10T00:01:08.8688687Z  help="GitHub repo where CI is running", 2025-10-10T00:01:08.8689312Z  ) 2025-10-10T00:01:08.8690515Z  parser.add_argument( 2025-10-10T00:01:08.8691262Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:01:08.8691995Z  ) 2025-10-10T00:01:08.8692494Z  parser.add_argument( 2025-10-10T00:01:08.8693246Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:01:08.8694020Z  ) 2025-10-10T00:01:08.8694494Z  parser.add_argument( 2025-10-10T00:01:08.8695274Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:01:08.8696058Z  ) 2025-10-10T00:01:08.8696520Z  parser.add_argument( 2025-10-10T00:01:08.8697298Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:01:08.8698304Z  ) 2025-10-10T00:01:08.8698777Z  parser.add_argument( 2025-10-10T00:01:08.8699319Z  "--github-ref-type", 2025-10-10T00:01:08.8700076Z  type=str, 2025-10-10T00:01:08.8700603Z  required=True, 2025-10-10T00:01:08.8701224Z  help="Current GitHub ref type, branch or tag", 2025-10-10T00:01:08.8701858Z  ) 2025-10-10T00:01:08.8702324Z  parser.add_argument( 2025-10-10T00:01:08.8702901Z  "--eligible-experiments", 2025-10-10T00:01:08.8703525Z  type=_str_comma_separated_to_set, 2025-10-10T00:01:08.8704125Z  required=False, 2025-10-10T00:01:08.8704666Z  default="", 2025-10-10T00:01:08.8705629Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:01:08.8706624Z  ) 2025-10-10T00:01:08.8707089Z  parser.add_argument( 2025-10-10T00:01:08.8707662Z  "--opt-out-experiments", 2025-10-10T00:01:08.8708267Z  type=_str_comma_separated_to_set, 2025-10-10T00:01:08.8708881Z  required=False, 2025-10-10T00:01:08.8709429Z  default="", 2025-10-10T00:01:08.8710191Z  help=( 2025-10-10T00:01:08.8710986Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:01:08.8712201Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:01:08.8713097Z  ), 2025-10-10T00:01:08.8713557Z  ) 2025-10-10T00:01:08.8714030Z  parser.add_argument( 2025-10-10T00:01:08.8714583Z  "--pr-number", 2025-10-10T00:01:08.8715134Z  type=str, 2025-10-10T00:01:08.8715671Z  required=False, 2025-10-10T00:01:08.8716209Z  default="", 2025-10-10T00:01:08.8716824Z  help="the optional PR number where this is run", 2025-10-10T00:01:08.8717455Z  ) 2025-10-10T00:01:08.8717900Z  2025-10-10T00:01:08.8718368Z  return parser.parse_args() 2025-10-10T00:01:08.8718935Z  2025-10-10T00:01:08.8719352Z  2025-10-10T00:01:08.8720712Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.8721739Z  auth = Auth.Token(github_token) 2025-10-10T00:01:08.8722359Z  return Github(auth=auth) 2025-10-10T00:01:08.8722904Z  2025-10-10T00:01:08.8723316Z  2025-10-10T00:01:08.8724062Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:01:08.8724940Z  repo = gh.get_repo(repo) 2025-10-10T00:01:08.8725567Z  return repo.get_issue(number=issue_num) 2025-10-10T00:01:08.8726159Z  2025-10-10T00:01:08.8726588Z  2025-10-10T00:01:08.8727051Z def get_potential_pr_author( 2025-10-10T00:01:08.8727811Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:01:08.8728560Z ) -> str: 2025-10-10T00:01:08.8729191Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:01:08.8730213Z  # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:01:08.8731068Z  # embedded in the tag name: ciflow// 2025-10-10T00:01:08.8731727Z  2025-10-10T00:01:08.8732205Z  gh = get_gh_client(github_token) 2025-10-10T00:01:08.8732795Z  2025-10-10T00:01:08.8733359Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:01:08.8734086Z  split_tag = ref_name.split("/") 2025-10-10T00:01:08.8734801Z  if ( 2025-10-10T00:01:08.8735294Z  len(split_tag) == 3 2025-10-10T00:01:08.8735895Z  and split_tag[0] == "ciflow" 2025-10-10T00:01:08.8736515Z  and split_tag[2].isnumeric() 2025-10-10T00:01:08.8737091Z  ): 2025-10-10T00:01:08.8737604Z  pr_number = split_tag[2] 2025-10-10T00:01:08.8738185Z  try: 2025-10-10T00:01:08.8738745Z  repository = gh.get_repo(repo) 2025-10-10T00:01:08.8739553Z  pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:01:08.8740394Z  except Exception as e: 2025-10-10T00:01:08.8741040Z  raise Exception( # noqa: TRY002 2025-10-10T00:01:08.8741813Z  f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:01:08.8742536Z  ) from e 2025-10-10T00:01:08.8743201Z  return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:01:08.8743995Z  # In all other cases, return the original input username 2025-10-10T00:01:08.8744658Z  return username 2025-10-10T00:01:08.8745167Z  2025-10-10T00:01:08.8745593Z  2025-10-10T00:01:08.8746094Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:01:08.8746701Z  """ 2025-10-10T00:01:08.8747459Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:01:08.8748316Z  """ 2025-10-10T00:01:08.8748977Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:01:08.8749919Z  2025-10-10T00:01:08.8750348Z  2025-10-10T00:01:08.8750821Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:01:08.8751415Z  try: 2025-10-10T00:01:08.8751912Z  data = yaml.safe_load(yaml_text) 2025-10-10T00:01:08.8752507Z  return data 2025-10-10T00:01:08.8753037Z  except yaml.YAMLError: 2025-10-10T00:01:08.8753643Z  log.exception("Error loading YAML") 2025-10-10T00:01:08.8754226Z  raise 2025-10-10T00:01:08.8754693Z  2025-10-10T00:01:08.8755105Z  2025-10-10T00:01:08.8755815Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:01:08.8756628Z  """ 2025-10-10T00:01:08.8757495Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:01:08.8758315Z  2025-10-10T00:01:08.8758939Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.8760046Z  and the text below is the list of opted in users. 2025-10-10T00:01:08.8760683Z  2025-10-10T00:01:08.8761351Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:01:08.8762137Z  """ 2025-10-10T00:01:08.8762711Z  rollout_state_parts = rollout_state.split("---") 2025-10-10T00:01:08.8763392Z  if len(rollout_state_parts) >= 2: 2025-10-10T00:01:08.8764092Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:01:08.8764757Z  else: 2025-10-10T00:01:08.8765244Z  return "", rollout_state 2025-10-10T00:01:08.8765790Z  2025-10-10T00:01:08.8766201Z  2025-10-10T00:01:08.8766680Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:01:08.8767272Z  """ 2025-10-10T00:01:08.8767891Z  Dictionary of users with a list of features they have opted into 2025-10-10T00:01:08.8768606Z  """ 2025-10-10T00:01:08.8769043Z  2025-10-10T00:01:08.8769547Z  2025-10-10T00:01:08.8770360Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:01:08.8771084Z  """ 2025-10-10T00:01:08.8771896Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:01:08.8772798Z  2025-10-10T00:01:08.8773677Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:01:08.8774746Z  - Example line: "@User1,lf,split_build" 2025-10-10T00:01:08.8775533Z  - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:01:08.8776233Z  2025-10-10T00:01:08.8776634Z  2025-10-10T00:01:08.8777059Z  """ 2025-10-10T00:01:08.8777534Z  optins = UserOptins() 2025-10-10T00:01:08.8778138Z  for user in user_optin_text.split("\n"): 2025-10-10T00:01:08.8778802Z  user = user.strip("\r\n\t -") 2025-10-10T00:01:08.8779652Z  if not user or not user.startswith("@"): 2025-10-10T00:01:08.8780406Z  # Not a valid user. Skip 2025-10-10T00:01:08.8780994Z  continue 2025-10-10T00:01:08.8781490Z  2025-10-10T00:01:08.8781908Z  if user: 2025-10-10T00:01:08.8782473Z  usr_name = user.split(",")[0].strip("@") 2025-10-10T00:01:08.8783254Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:01:08.8783968Z  2025-10-10T00:01:08.8784409Z  return optins 2025-10-10T00:01:08.8784906Z  2025-10-10T00:01:08.8785318Z  2025-10-10T00:01:08.8785894Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:01:08.8786581Z  """ 2025-10-10T00:01:08.8787085Z  Check if the experiment name is valid. 2025-10-10T00:01:08.8787691Z  A valid name: 2025-10-10T00:01:08.8788456Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:01:08.8789569Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:01:08.8790436Z  - Cannot contain spaces 2025-10-10T00:01:08.8790988Z  """ 2025-10-10T00:01:08.8791429Z  2025-10-10T00:01:08.8791964Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:01:08.8792769Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:01:08.8793593Z  2025-10-10T00:01:08.8794072Z  if valid: 2025-10-10T00:01:08.8794565Z  return True 2025-10-10T00:01:08.8795339Z  2025-10-10T00:01:08.8795829Z  log.error( 2025-10-10T00:01:08.8797336Z  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-10-10T00:01:08.8798910Z  ) 2025-10-10T00:01:08.8799357Z  return False 2025-10-10T00:01:08.8799944Z  2025-10-10T00:01:08.8800346Z  2025-10-10T00:01:08.8800940Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:01:08.8801641Z  """ 2025-10-10T00:01:08.8802329Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:01:08.8803103Z  """ 2025-10-10T00:01:08.8803552Z  try: 2025-10-10T00:01:08.8804015Z  if settings_text: 2025-10-10T00:01:08.8804851Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:01:08.8805746Z  # for easy reading 2025-10-10T00:01:08.8806647Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:01:08.8807725Z  # the backtick character in shell commands. 2025-10-10T00:01:08.8808427Z  backtick = chr(96) # backtick character 2025-10-10T00:01:08.8809210Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:01:08.8810058Z  settings = load_yaml(settings_text) 2025-10-10T00:01:08.8810647Z  2025-10-10T00:01:08.8811326Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:01:08.8812136Z  experiments = {} 2025-10-10T00:01:08.8812670Z  2025-10-10T00:01:08.8813316Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:01:08.8814155Z  if not is_valid_experiment_name(exp_name): 2025-10-10T00:01:08.8815340Z  # 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-10-10T00:01:08.8816437Z  continue 2025-10-10T00:01:08.8816992Z  2025-10-10T00:01:08.8817460Z  valid_settings = {} 2025-10-10T00:01:08.8818080Z  for setting in exp_settings: 2025-10-10T00:01:08.8818731Z  if setting not in Experiment._fields: 2025-10-10T00:01:08.8819370Z  log.warning( 2025-10-10T00:01:08.8820336Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:01:08.8821126Z  ) 2025-10-10T00:01:08.8821656Z  else: 2025-10-10T00:01:08.8822299Z  valid_settings[setting] = exp_settings[setting] 2025-10-10T00:01:08.8822961Z  2025-10-10T00:01:08.8823527Z  experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:01:08.8824254Z  return Settings(experiments) 2025-10-10T00:01:08.8824833Z  2025-10-10T00:01:08.8825276Z  except Exception: 2025-10-10T00:01:08.8825874Z  log.exception("Failed to parse settings") 2025-10-10T00:01:08.8826470Z  2025-10-10T00:01:08.8826899Z  return Settings() 2025-10-10T00:01:08.8827395Z  2025-10-10T00:01:08.8827812Z  2025-10-10T00:01:08.8828477Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:01:08.8829128Z  """ 2025-10-10T00:01:08.8829891Z  Parse settings, if any, from the rollout state. 2025-10-10T00:01:08.8830520Z  2025-10-10T00:01:08.8831146Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:08.8831985Z  and the text below is the list of opted in users. 2025-10-10T00:01:08.8832619Z  2025-10-10T00:01:08.8833299Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:01:08.8834091Z  """ 2025-10-10T00:01:08.8834742Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.8835595Z  return parse_settings_from_text(settings_text) 2025-10-10T00:01:08.8836223Z  2025-10-10T00:01:08.8836633Z  2025-10-10T00:01:08.8837171Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:01:08.8837831Z  """ 2025-10-10T00:01:08.8838322Z  Parse users from the rollout state. 2025-10-10T00:01:08.8838888Z  2025-10-10T00:01:08.8839310Z  """ 2025-10-10T00:01:08.8840036Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:08.8840865Z  return parse_user_opt_in_from_text(users_text) 2025-10-10T00:01:08.8841614Z  2025-10-10T00:01:08.8842017Z  2025-10-10T00:01:08.8842722Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.8843540Z  """ 2025-10-10T00:01:08.8844068Z  Check if a user is opted into an experiment 2025-10-10T00:01:08.8844671Z  """ 2025-10-10T00:01:08.8845243Z  return experiment_name in user_optins.get(user, []) 2025-10-10T00:01:08.8845881Z  2025-10-10T00:01:08.8846293Z  2025-10-10T00:01:08.8847012Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:08.8847826Z  """ 2025-10-10T00:01:08.8848397Z  Check if a user explicitly opted out of an experiment 2025-10-10T00:01:08.8849038Z  """ 2025-10-10T00:01:08.8849842Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:01:08.8850649Z  experiment_optout = "-" + experiment_name 2025-10-10T00:01:08.8851389Z  if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:01:08.8852058Z  return False 2025-10-10T00:01:08.8852556Z  2025-10-10T00:01:08.8853101Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:01:08.8853772Z  log.warning( 2025-10-10T00:01:08.8854691Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:01:08.8855619Z  ) 2025-10-10T00:01:08.8856068Z  2025-10-10T00:01:08.8856492Z  return True 2025-10-10T00:01:08.8856959Z  2025-10-10T00:01:08.8857373Z  2025-10-10T00:01:08.8857804Z def get_runner_prefix( 2025-10-10T00:01:08.8858368Z  rollout_state: str, 2025-10-10T00:01:08.8858949Z  workflow_requestors: Iterable[str], 2025-10-10T00:01:08.8859766Z  branch: str, 2025-10-10T00:01:08.8860395Z  eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.8861172Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:08.8861854Z  is_canary: bool = False, 2025-10-10T00:01:08.8862398Z ) -> str: 2025-10-10T00:01:08.8862954Z  settings = parse_settings(rollout_state) 2025-10-10T00:01:08.8863605Z  user_optins = parse_users(rollout_state) 2025-10-10T00:01:08.8864198Z  2025-10-10T00:01:08.8864760Z  fleet_prefix = "" 2025-10-10T00:01:08.8865301Z  prefixes = [] 2025-10-10T00:01:08.8866050Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:01:08.8867053Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:01:08.8867838Z  log.info( 2025-10-10T00:01:08.8868646Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:01:08.8869563Z  ) 2025-10-10T00:01:08.8870122Z  continue 2025-10-10T00:01:08.8870637Z  2025-10-10T00:01:08.8871099Z  if opt_out_experiments: 2025-10-10T00:01:08.8871736Z  if experiment_name in opt_out_experiments: 2025-10-10T00:01:08.8872457Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:01:08.8873136Z  log.info( 2025-10-10T00:01:08.8874143Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:01:08.8875160Z  ) 2025-10-10T00:01:08.8875662Z  continue 2025-10-10T00:01:08.8876176Z  2025-10-10T00:01:08.8876795Z  if eligible_experiments: 2025-10-10T00:01:08.8877455Z  if experiment_name not in eligible_experiments: 2025-10-10T00:01:08.8878171Z  exp_list = ", ".join(eligible_experiments) 2025-10-10T00:01:08.8878809Z  log.info( 2025-10-10T00:01:08.8879873Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:01:08.8880768Z  ) 2025-10-10T00:01:08.8881276Z  continue 2025-10-10T00:01:08.8881872Z  elif not experiment_settings.default: 2025-10-10T00:01:08.8882479Z  log.info( 2025-10-10T00:01:08.8883241Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:01:08.8884033Z  ) 2025-10-10T00:01:08.8884530Z  continue 2025-10-10T00:01:08.8885023Z  2025-10-10T00:01:08.8885570Z  # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:01:08.8886252Z  opted_out_users = [ 2025-10-10T00:01:08.8886792Z  requestor 2025-10-10T00:01:08.8887356Z  for requestor in workflow_requestors 2025-10-10T00:01:08.8888112Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.8888809Z  ] 2025-10-10T00:01:08.8889242Z  2025-10-10T00:01:08.8889788Z  if opted_out_users: 2025-10-10T00:01:08.8890338Z  log.info( 2025-10-10T00:01:08.8891061Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:01:08.8891819Z  ) 2025-10-10T00:01:08.8892293Z  continue 2025-10-10T00:01:08.8892787Z  2025-10-10T00:01:08.8893342Z  # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:01:08.8894028Z  opted_in_users = [ 2025-10-10T00:01:08.8894575Z  requestor 2025-10-10T00:01:08.8895156Z  for requestor in workflow_requestors 2025-10-10T00:01:08.8895898Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:01:08.8896591Z  ] 2025-10-10T00:01:08.8897026Z  2025-10-10T00:01:08.8897495Z  enabled = False 2025-10-10T00:01:08.8898022Z  if opted_in_users: 2025-10-10T00:01:08.8898694Z  log.info( 2025-10-10T00:01:08.8899418Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:01:08.8900245Z  ) 2025-10-10T00:01:08.8900734Z  enabled = True 2025-10-10T00:01:08.8901288Z  2025-10-10T00:01:08.8901774Z  elif experiment_settings.rollout_perc: 2025-10-10T00:01:08.8902679Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:01:08.8903686Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:01:08.8904413Z  log.info( 2025-10-10T00:01:08.8905369Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:01:08.8906333Z  ) 2025-10-10T00:01:08.8906839Z  enabled = True 2025-10-10T00:01:08.8907377Z  2025-10-10T00:01:08.8907818Z  if enabled: 2025-10-10T00:01:08.8908349Z  label = experiment_name 2025-10-10T00:01:08.8908994Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:01:08.8910148Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:01:08.8911251Z  # - If it's enabled, then we always list it's prefix first 2025-10-10T00:01:08.8912097Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:01:08.8912819Z  if is_canary: 2025-10-10T00:01:08.8913425Z  label += CANARY_FLEET_SUFFIX 2025-10-10T00:01:08.8914048Z  fleet_prefix = label 2025-10-10T00:01:08.8914616Z  else: 2025-10-10T00:01:08.8915156Z  prefixes.append(label) 2025-10-10T00:01:08.8915742Z  2025-10-10T00:01:08.8916186Z  if len(prefixes) > 1: 2025-10-10T00:01:08.8916723Z  log.error( 2025-10-10T00:01:08.8917849Z  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-10-10T00:01:08.8918992Z  ) 2025-10-10T00:01:08.8919689Z  prefixes = prefixes[:1] 2025-10-10T00:01:08.8920256Z  2025-10-10T00:01:08.8920717Z  # Fleet always comes first 2025-10-10T00:01:08.8921288Z  if fleet_prefix: 2025-10-10T00:01:08.8921845Z  prefixes.insert(0, fleet_prefix) 2025-10-10T00:01:08.8922421Z  2025-10-10T00:01:08.8922945Z  return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:01:08.8923603Z  2025-10-10T00:01:08.8924017Z  2025-10-10T00:01:08.8924720Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:01:08.8925547Z  """ 2025-10-10T00:01:08.8926256Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:01:08.8927037Z  2025-10-10T00:01:08.8927694Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:01:08.8928459Z  """ 2025-10-10T00:01:08.8928942Z  gh = get_gh_client(github_token) 2025-10-10T00:01:08.8929717Z  issue = get_issue(gh, repo, issue_num) 2025-10-10T00:01:08.8930476Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:01:08.8931158Z  2025-10-10T00:01:08.8931573Z  2025-10-10T00:01:08.8932266Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:01:08.8933218Z  for _ in range(num_retries): 2025-10-10T00:01:08.8933782Z  try: 2025-10-10T00:01:08.8934319Z  req = Request(url=url, headers=headers) 2025-10-10T00:01:08.8935046Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:01:08.8935781Z  return json.loads(content) 2025-10-10T00:01:08.8936410Z  except Exception as e: 2025-10-10T00:01:08.8937055Z  log.warning(f"Could not download {url}: {e}") 2025-10-10T00:01:08.8937677Z  2025-10-10T00:01:08.8938313Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:01:08.8939111Z  return {} 2025-10-10T00:01:08.8939691Z  2025-10-10T00:01:08.8940108Z  2025-10-10T00:01:08.8940522Z @cache 2025-10-10T00:01:08.8941248Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:01:08.8942059Z  """ 2025-10-10T00:01:08.8942538Z  Dynamically get PR information 2025-10-10T00:01:08.8943112Z  """ 2025-10-10T00:01:08.8943717Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:01:08.8944418Z  headers = { 2025-10-10T00:01:08.8944981Z  "Accept": "application/vnd.github.v3+json", 2025-10-10T00:01:08.8945794Z  "Authorization": f"token {github_token}", 2025-10-10T00:01:08.8946508Z  } 2025-10-10T00:01:08.8947162Z  json_response: dict[str, Any] = download_json( 2025-10-10T00:01:08.8947862Z  url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:01:08.8948478Z  headers=headers, 2025-10-10T00:01:08.8949003Z  ) 2025-10-10T00:01:08.8949427Z  2025-10-10T00:01:08.8950049Z  if not json_response: 2025-10-10T00:01:08.8950734Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:01:08.8951437Z  return {} 2025-10-10T00:01:08.8951941Z  2025-10-10T00:01:08.8952373Z  return json_response 2025-10-10T00:01:08.8952893Z  2025-10-10T00:01:08.8953304Z  2025-10-10T00:01:08.8953980Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:01:08.8954754Z  """ 2025-10-10T00:01:08.8955388Z  Dynamically get the latest list of labels from the pull request 2025-10-10T00:01:08.8956100Z  """ 2025-10-10T00:01:08.8956676Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:01:08.8957362Z  return { 2025-10-10T00:01:08.8958043Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:01:08.8958798Z  } 2025-10-10T00:01:08.8959237Z  2025-10-10T00:01:08.8959815Z  2025-10-10T00:01:08.8960258Z def main() -> None: 2025-10-10T00:01:08.8960785Z  args = parse_args() 2025-10-10T00:01:08.8961301Z  2025-10-10T00:01:08.8961795Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:01:08.8962393Z  2025-10-10T00:01:08.8962893Z  # Check if the PR is opt-out 2025-10-10T00:01:08.8963466Z  if args.pr_number: 2025-10-10T00:01:08.8964220Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:01:08.8965036Z  if OPT_OUT_LABEL in labels: 2025-10-10T00:01:08.8965655Z  log.info( 2025-10-10T00:01:08.8966485Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:01:08.8967311Z  ) 2025-10-10T00:01:08.8967968Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.8968716Z  sys.exit() 2025-10-10T00:01:08.8969364Z  2025-10-10T00:01:08.8969923Z  try: 2025-10-10T00:01:08.8970470Z  rollout_state = get_rollout_state_from_issue( 2025-10-10T00:01:08.8971259Z  args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:01:08.8971964Z  ) 2025-10-10T00:01:08.8972408Z  2025-10-10T00:01:08.8972894Z  username = get_potential_pr_author( 2025-10-10T00:01:08.8973532Z  args.github_token, 2025-10-10T00:01:08.8974115Z  args.github_repo, 2025-10-10T00:01:08.8974689Z  args.github_actor, 2025-10-10T00:01:08.8975272Z  args.github_ref_type, 2025-10-10T00:01:08.8975864Z  args.github_branch, 2025-10-10T00:01:08.8976419Z  ) 2025-10-10T00:01:08.8976861Z  2025-10-10T00:01:08.8977419Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:01:08.8978091Z  2025-10-10T00:01:08.8978581Z  runner_label_prefix = get_runner_prefix( 2025-10-10T00:01:08.8979214Z  rollout_state, 2025-10-10T00:01:08.8979903Z  (args.github_issue_owner, username), 2025-10-10T00:01:08.8980528Z  args.github_branch, 2025-10-10T00:01:08.8981255Z  args.eligible_experiments, 2025-10-10T00:01:08.8981868Z  args.opt_out_experiments, 2025-10-10T00:01:08.8982464Z  is_canary, 2025-10-10T00:01:08.8982963Z  ) 2025-10-10T00:01:08.8983436Z  2025-10-10T00:01:08.8983878Z  except Exception as e: 2025-10-10T00:01:08.8984549Z  log.error( 2025-10-10T00:01:08.8985333Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:01:08.8986146Z  ) 2025-10-10T00:01:08.8986604Z  2025-10-10T00:01:08.8987198Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:08.8987931Z  2025-10-10T00:01:08.8988342Z  2025-10-10T00:01:08.8988780Z if __name__ == "__main__": 2025-10-10T00:01:08.8989311Z  main() 2025-10-10T00:01:08.8989965Z  2025-10-10T00:01:08.8990388Z EOF 2025-10-10T00:01:08.8990821Z  2025-10-10T00:01:08.8991261Z cat runner_determinator.py 2025-10-10T00:01:09.0547442Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:09.0548389Z env: 2025-10-10T00:01:09.0549673Z GITHUB_TOKEN: *** 2025-10-10T00:01:09.0550207Z ISSUE_NUMBER: 5132 2025-10-10T00:01:09.0550751Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:09.0551340Z ISSUE_OWNER: 2025-10-10T00:01:09.0551837Z CHECK_EXPERIMENTS: 2025-10-10T00:01:09.0552363Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:01:09.0552883Z PR_NUMBER: 2025-10-10T00:01:09.0553344Z ##[endgroup] 2025-10-10T00:01:09.0780740Z # flake8: noqa: G004 2025-10-10T00:01:09.0781063Z 2025-10-10T00:01:09.0781476Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:01:09.0782410Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:01:09.0783167Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:01:09.0783616Z 2025-10-10T00:01:09.0783765Z """ 2025-10-10T00:01:09.0784310Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:01:09.0785153Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:01:09.0786035Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:01:09.0786795Z of which runners should be used to run which job. 2025-10-10T00:01:09.0787186Z 2025-10-10T00:01:09.0787549Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:01:09.0788587Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:01:09.0789423Z settings are considered to be empty with only the second part, the user 2025-10-10T00:01:09.0790288Z list, defined. 2025-10-10T00:01:09.0790500Z 2025-10-10T00:01:09.0790844Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:01:09.0791704Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:01:09.0792494Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:01:09.0792920Z 2025-10-10T00:01:09.0793267Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:01:09.0794096Z The user list is also a comma separated list of additional features or 2025-10-10T00:01:09.0794783Z experiments which the user could be opted in to. 2025-10-10T00:01:09.0795162Z 2025-10-10T00:01:09.0795355Z The user list has the following rules: 2025-10-10T00:01:09.0795676Z 2025-10-10T00:01:09.0795970Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:01:09.0796785Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:01:09.0797503Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:01:09.0797870Z 2025-10-10T00:01:09.0798026Z Example config: 2025-10-10T00:01:09.0798451Z # A list of experiments that can be opted into. 2025-10-10T00:01:09.0799215Z # This defines the behavior they'll induce when opted into. 2025-10-10T00:01:09.0800000Z # Expected syntax is: 2025-10-10T00:01:09.0800615Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:01:09.0801549Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:01:09.0802131Z 2025-10-10T00:01:09.0802292Z experiments: 2025-10-10T00:01:09.0802646Z lf: 2025-10-10T00:01:09.0802996Z rollout_percent: 25 2025-10-10T00:01:09.0803416Z all_branches: false 2025-10-10T00:01:09.0803839Z default: true 2025-10-10T00:01:09.0804216Z --- 2025-10-10T00:01:09.0804409Z 2025-10-10T00:01:09.0804566Z # Opt-ins: 2025-10-10T00:01:09.0805114Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:01:09.0805937Z # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:01:09.0806671Z # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:01:09.0807301Z # Experiments should be from the above list. 2025-10-10T00:01:09.0807660Z 2025-10-10T00:01:09.0807833Z @User1,-lf,split_build 2025-10-10T00:01:09.0808237Z @User2,lf 2025-10-10T00:01:09.0808589Z @User3,split_build 2025-10-10T00:01:09.0809015Z """ 2025-10-10T00:01:09.0809222Z 2025-10-10T00:01:09.0809401Z import json 2025-10-10T00:01:09.0810467Z import logging 2025-10-10T00:01:09.0810834Z import os 2025-10-10T00:01:09.0811165Z import random 2025-10-10T00:01:09.0811517Z import re 2025-10-10T00:01:09.0811846Z import sys 2025-10-10T00:01:09.0812231Z from argparse import ArgumentParser 2025-10-10T00:01:09.0812736Z from collections.abc import Iterable 2025-10-10T00:01:09.0813221Z from functools import cache 2025-10-10T00:01:09.0813667Z from logging import LogRecord 2025-10-10T00:01:09.0814119Z from typing import Any, NamedTuple 2025-10-10T00:01:09.0814625Z from urllib.request import Request, urlopen 2025-10-10T00:01:09.0814977Z 2025-10-10T00:01:09.0815128Z import yaml 2025-10-10T00:01:09.0815492Z from github import Auth, Github 2025-10-10T00:01:09.0815943Z from github.Issue import Issue 2025-10-10T00:01:09.0816234Z 2025-10-10T00:01:09.0816240Z 2025-10-10T00:01:09.0816442Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:01:09.0817097Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:01:09.0817917Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:01:09.0818457Z 2025-10-10T00:01:09.0818670Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:01:09.0819338Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:01:09.0820033Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:01:09.0820554Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:01:09.0820899Z 2025-10-10T00:01:09.0821082Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:01:09.0821393Z 2025-10-10T00:01:09.0821573Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:01:09.0822006Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:01:09.0822269Z 2025-10-10T00:01:09.0822281Z 2025-10-10T00:01:09.0822458Z class Experiment(NamedTuple): 2025-10-10T00:01:09.0822903Z rollout_perc: float = ( 2025-10-10T00:01:09.0823507Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:01:09.0824137Z ) 2025-10-10T00:01:09.0824474Z all_branches: bool = ( 2025-10-10T00:01:09.0825058Z False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:01:09.0825689Z ) 2025-10-10T00:01:09.0826019Z default: bool = ( 2025-10-10T00:01:09.0826547Z True # If True, the experiment is enabled by default for all queries 2025-10-10T00:01:09.0827157Z ) 2025-10-10T00:01:09.0827332Z 2025-10-10T00:01:09.0827499Z # Add more fields as needed 2025-10-10T00:01:09.0827783Z 2025-10-10T00:01:09.0827789Z 2025-10-10T00:01:09.0827961Z class Settings(NamedTuple): 2025-10-10T00:01:09.0828364Z """ 2025-10-10T00:01:09.0828907Z Settings for the experiments that can be opted into. 2025-10-10T00:01:09.0829441Z """ 2025-10-10T00:01:09.0829906Z 2025-10-10T00:01:09.0830106Z experiments: dict[str, Experiment] = {} 2025-10-10T00:01:09.0830453Z 2025-10-10T00:01:09.0830466Z 2025-10-10T00:01:09.0830663Z class ColorFormatter(logging.Formatter): 2025-10-10T00:01:09.0831253Z """Color codes the log messages based on the log level""" 2025-10-10T00:01:09.0831671Z 2025-10-10T00:01:09.0831823Z COLORS = { 2025-10-10T00:01:09.0832190Z "WARNING": "\033[33m", # Yellow 2025-10-10T00:01:09.0832663Z "ERROR": "\033[31m", # Red 2025-10-10T00:01:09.0833135Z "CRITICAL": "\033[31m", # Red 2025-10-10T00:01:09.0833597Z "INFO": "\033[0m", # Reset 2025-10-10T00:01:09.0834049Z "DEBUG": "\033[0m", # Reset 2025-10-10T00:01:09.0834478Z } 2025-10-10T00:01:09.0834661Z 2025-10-10T00:01:09.0834860Z def format(self, record: LogRecord) -> str: 2025-10-10T00:01:09.0835567Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:01:09.0836300Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:01:09.0836840Z return super().format(record) 2025-10-10T00:01:09.0837157Z 2025-10-10T00:01:09.0837163Z 2025-10-10T00:01:09.0837345Z handler = logging.StreamHandler() 2025-10-10T00:01:09.0838005Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:01:09.0838525Z 2025-10-10T00:01:09.0838749Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:01:09.0839299Z log.addHandler(handler) 2025-10-10T00:01:09.0839932Z log.setLevel(logging.INFO) 2025-10-10T00:01:09.0840208Z 2025-10-10T00:01:09.0840214Z 2025-10-10T00:01:09.0840446Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:01:09.0840974Z """ 2025-10-10T00:01:09.0841436Z Defines outputs of the github action that invokes this script 2025-10-10T00:01:09.0842022Z """ 2025-10-10T00:01:09.0842352Z if not GITHUB_OUTPUT: 2025-10-10T00:01:09.0843358Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:01:09.0844405Z log.warning( 2025-10-10T00:01:09.0845210Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:01:09.0846080Z ) 2025-10-10T00:01:09.0855892Z print(f"::set-output name={key}::{value}") 2025-10-10T00:01:09.0856459Z return 2025-10-10T00:01:09.0856684Z 2025-10-10T00:01:09.0857062Z with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:01:09.0857621Z log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:01:09.0858165Z f.write(f"{key}={value}\n") 2025-10-10T00:01:09.0858480Z 2025-10-10T00:01:09.0858486Z 2025-10-10T00:01:09.0858770Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:01:09.0859373Z return frozenset( 2025-10-10T00:01:09.0860261Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:01:09.0860918Z ) 2025-10-10T00:01:09.0861097Z 2025-10-10T00:01:09.0861104Z 2025-10-10T00:01:09.0861275Z def parse_args() -> Any: 2025-10-10T00:01:09.0861803Z parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:01:09.0862623Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:01:09.0863358Z parser.add_argument( 2025-10-10T00:01:09.0863782Z "--github-issue-repo", 2025-10-10T00:01:09.0864205Z type=str, 2025-10-10T00:01:09.0864571Z required=False, 2025-10-10T00:01:09.0864983Z default="pytorch/test-infra", 2025-10-10T00:01:09.0865483Z help="GitHub repo to get the issue", 2025-10-10T00:01:09.0865950Z ) 2025-10-10T00:01:09.0866286Z parser.add_argument( 2025-10-10T00:01:09.0866689Z "--github-repo", 2025-10-10T00:01:09.0867083Z type=str, 2025-10-10T00:01:09.0867435Z required=True, 2025-10-10T00:01:09.0867995Z help="GitHub repo where CI is running", 2025-10-10T00:01:09.0868478Z ) 2025-10-10T00:01:09.0868815Z parser.add_argument( 2025-10-10T00:01:09.0869388Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:01:09.0870333Z ) 2025-10-10T00:01:09.0870684Z parser.add_argument( 2025-10-10T00:01:09.0871262Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:01:09.0871900Z ) 2025-10-10T00:01:09.0872226Z parser.add_argument( 2025-10-10T00:01:09.0872819Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:01:09.0873458Z ) 2025-10-10T00:01:09.0873793Z parser.add_argument( 2025-10-10T00:01:09.0874402Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:01:09.0875075Z ) 2025-10-10T00:01:09.0875409Z parser.add_argument( 2025-10-10T00:01:09.0875819Z "--github-ref-type", 2025-10-10T00:01:09.0876245Z type=str, 2025-10-10T00:01:09.0876599Z required=True, 2025-10-10T00:01:09.0877052Z help="Current GitHub ref type, branch or tag", 2025-10-10T00:01:09.0877552Z ) 2025-10-10T00:01:09.0877885Z parser.add_argument( 2025-10-10T00:01:09.0878305Z "--eligible-experiments", 2025-10-10T00:01:09.0878786Z type=_str_comma_separated_to_set, 2025-10-10T00:01:09.0879277Z required=False, 2025-10-10T00:01:09.0880080Z default="", 2025-10-10T00:01:09.0880889Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:01:09.0881764Z ) 2025-10-10T00:01:09.0882097Z parser.add_argument( 2025-10-10T00:01:09.0882515Z "--opt-out-experiments", 2025-10-10T00:01:09.0940383Z type=_str_comma_separated_to_set, 2025-10-10T00:01:09.0941144Z required=False, 2025-10-10T00:01:09.0941582Z default="", 2025-10-10T00:01:09.0941989Z help=( 2025-10-10T00:01:09.0942668Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:01:09.0943760Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:01:09.0944553Z ), 2025-10-10T00:01:09.0944878Z ) 2025-10-10T00:01:09.0945214Z parser.add_argument( 2025-10-10T00:01:09.0945621Z "--pr-number", 2025-10-10T00:01:09.0945995Z type=str, 2025-10-10T00:01:09.0946366Z required=False, 2025-10-10T00:01:09.0946750Z default="", 2025-10-10T00:01:09.0947532Z help="the optional PR number where this is run", 2025-10-10T00:01:09.0948068Z ) 2025-10-10T00:01:09.0948255Z 2025-10-10T00:01:09.0948433Z return parser.parse_args() 2025-10-10T00:01:09.0948717Z 2025-10-10T00:01:09.0948724Z 2025-10-10T00:01:09.0949114Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:01:09.0949969Z auth = Auth.Token(github_token) 2025-10-10T00:01:09.0950445Z return Github(auth=auth) 2025-10-10T00:01:09.0950719Z 2025-10-10T00:01:09.0950725Z 2025-10-10T00:01:09.0951154Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:01:09.0951897Z repo = gh.get_repo(repo) 2025-10-10T00:01:09.0952348Z return repo.get_issue(number=issue_num) 2025-10-10T00:01:09.0952690Z 2025-10-10T00:01:09.0952696Z 2025-10-10T00:01:09.0952870Z def get_potential_pr_author( 2025-10-10T00:01:09.0953471Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:01:09.0954090Z ) -> str: 2025-10-10T00:01:09.0954566Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:01:09.0955325Z # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:01:09.0956013Z # embedded in the tag name: ciflow// 2025-10-10T00:01:09.0956399Z 2025-10-10T00:01:09.0956743Z gh = get_gh_client(github_token) 2025-10-10T00:01:09.0957062Z 2025-10-10T00:01:09.0957308Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:01:09.0957882Z split_tag = ref_name.split("/") 2025-10-10T00:01:09.0958340Z if ( 2025-10-10T00:01:09.0958692Z len(split_tag) == 3 2025-10-10T00:01:09.0959129Z and split_tag[0] == "ciflow" 2025-10-10T00:01:09.0959778Z and split_tag[2].isnumeric() 2025-10-10T00:01:09.0960233Z ): 2025-10-10T00:01:09.0960582Z pr_number = split_tag[2] 2025-10-10T00:01:09.0961021Z try: 2025-10-10T00:01:09.0961417Z repository = gh.get_repo(repo) 2025-10-10T00:01:09.0961989Z pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:01:09.0962540Z except Exception as e: 2025-10-10T00:01:09.0963021Z raise Exception( # noqa: TRY002 2025-10-10T00:01:09.0963638Z f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:01:09.0964247Z ) from e 2025-10-10T00:01:09.0964735Z return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:01:09.0965388Z # In all other cases, return the original input username 2025-10-10T00:01:09.0965934Z return username 2025-10-10T00:01:09.0966161Z 2025-10-10T00:01:09.0966168Z 2025-10-10T00:01:09.0966372Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:01:09.0966864Z """ 2025-10-10T00:01:09.0967456Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:01:09.0968176Z """ 2025-10-10T00:01:09.0968675Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:01:09.0969172Z 2025-10-10T00:01:09.0969178Z 2025-10-10T00:01:09.0969358Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:01:09.0969926Z try: 2025-10-10T00:01:09.0970290Z data = yaml.safe_load(yaml_text) 2025-10-10T00:01:09.0970767Z return data 2025-10-10T00:01:09.0971143Z except yaml.YAMLError: 2025-10-10T00:01:09.0971584Z log.exception("Error loading YAML") 2025-10-10T00:01:09.0972053Z raise 2025-10-10T00:01:09.0972255Z 2025-10-10T00:01:09.0972262Z 2025-10-10T00:01:09.0972648Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:01:09.0973331Z """ 2025-10-10T00:01:09.0973906Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:01:09.0974474Z 2025-10-10T00:01:09.0974970Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:09.0975679Z and the text below is the list of opted in users. 2025-10-10T00:01:09.0976053Z 2025-10-10T00:01:09.0976413Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:01:09.0977054Z """ 2025-10-10T00:01:09.0977453Z rollout_state_parts = rollout_state.split("---") 2025-10-10T00:01:09.0978009Z if len(rollout_state_parts) >= 2: 2025-10-10T00:01:09.0978564Z return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:01:09.0979116Z else: 2025-10-10T00:01:09.0979561Z return "", rollout_state 2025-10-10T00:01:09.0979852Z 2025-10-10T00:01:09.0979859Z 2025-10-10T00:01:09.0980051Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:01:09.0980515Z """ 2025-10-10T00:01:09.0980994Z Dictionary of users with a list of features they have opted into 2025-10-10T00:01:09.0981587Z """ 2025-10-10T00:01:09.0981766Z 2025-10-10T00:01:09.0981772Z 2025-10-10T00:01:09.0982091Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:01:09.0982692Z """ 2025-10-10T00:01:09.0983348Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-10-10T00:01:09.0983988Z 2025-10-10T00:01:09.0984578Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:01:09.0985710Z - Example line: "@User1,lf,split_build" 2025-10-10T00:01:09.0986344Z - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:01:09.0986789Z 2025-10-10T00:01:09.0986796Z 2025-10-10T00:01:09.0986946Z """ 2025-10-10T00:01:09.0987283Z optins = UserOptins() 2025-10-10T00:01:09.0987734Z for user in user_optin_text.split("\n"): 2025-10-10T00:01:09.0988245Z user = user.strip("\r\n\t -") 2025-10-10T00:01:09.0988750Z if not user or not user.startswith("@"): 2025-10-10T00:01:09.0989271Z # Not a valid user. Skip 2025-10-10T00:01:09.0989926Z continue 2025-10-10T00:01:09.0990152Z 2025-10-10T00:01:09.0990297Z if user: 2025-10-10T00:01:09.0990702Z usr_name = user.split(",")[0].strip("@") 2025-10-10T00:01:09.0991358Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:01:09.0991822Z 2025-10-10T00:01:09.0991994Z return optins 2025-10-10T00:01:09.0992211Z 2025-10-10T00:01:09.0992220Z 2025-10-10T00:01:09.0992492Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:01:09.0993043Z """ 2025-10-10T00:01:09.0993405Z Check if the experiment name is valid. 2025-10-10T00:01:09.0993886Z A valid name: 2025-10-10T00:01:09.0994481Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:01:09.0995365Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:01:09.0996035Z - Cannot contain spaces 2025-10-10T00:01:09.0996461Z """ 2025-10-10T00:01:09.0996637Z 2025-10-10T00:01:09.0996879Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:01:09.0997530Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:01:09.0997947Z 2025-10-10T00:01:09.0998090Z if valid: 2025-10-10T00:01:09.0998441Z return True 2025-10-10T00:01:09.0998659Z 2025-10-10T00:01:09.0998811Z log.error( 2025-10-10T00:01:09.1000296Z 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-10-10T00:01:09.1001863Z ) 2025-10-10T00:01:09.1002180Z return False 2025-10-10T00:01:09.1002396Z 2025-10-10T00:01:09.1002403Z 2025-10-10T00:01:09.1002685Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:01:09.1003270Z """ 2025-10-10T00:01:09.1003934Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:01:09.1004607Z """ 2025-10-10T00:01:09.1004922Z try: 2025-10-10T00:01:09.1005258Z if settings_text: 2025-10-10T00:01:09.1005928Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:01:09.1006677Z # for easy reading 2025-10-10T00:01:09.1007419Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:01:09.1008248Z # the backtick character in shell commands. 2025-10-10T00:01:09.1008806Z backtick = chr(96) # backtick character 2025-10-10T00:01:09.1009414Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:01:09.1010725Z settings = load_yaml(settings_text) 2025-10-10T00:01:09.1011075Z 2025-10-10T00:01:09.1011458Z # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:01:09.1012158Z experiments = {} 2025-10-10T00:01:09.1012425Z 2025-10-10T00:01:09.1012810Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:01:09.1013499Z if not is_valid_experiment_name(exp_name): 2025-10-10T00:01:09.1014551Z # 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-10-10T00:01:09.1015680Z continue 2025-10-10T00:01:09.1015950Z 2025-10-10T00:01:09.1016119Z valid_settings = {} 2025-10-10T00:01:09.1016588Z for setting in exp_settings: 2025-10-10T00:01:09.1017113Z if setting not in Experiment._fields: 2025-10-10T00:01:09.1017626Z log.warning( 2025-10-10T00:01:09.1018279Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:01:09.1018945Z ) 2025-10-10T00:01:09.1019341Z else: 2025-10-10T00:01:09.1019929Z valid_settings[setting] = exp_settings[setting] 2025-10-10T00:01:09.1020327Z 2025-10-10T00:01:09.1020581Z experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:01:09.1021161Z return Settings(experiments) 2025-10-10T00:01:09.1021485Z 2025-10-10T00:01:09.1021651Z except Exception: 2025-10-10T00:01:09.1022080Z log.exception("Failed to parse settings") 2025-10-10T00:01:09.1022434Z 2025-10-10T00:01:09.1022596Z return Settings() 2025-10-10T00:01:09.1022822Z 2025-10-10T00:01:09.1022828Z 2025-10-10T00:01:09.1023054Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:01:09.1023574Z """ 2025-10-10T00:01:09.1023965Z Parse settings, if any, from the rollout state. 2025-10-10T00:01:09.1024339Z 2025-10-10T00:01:09.1024662Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:01:09.1025370Z and the text below is the list of opted in users. 2025-10-10T00:01:09.1025744Z 2025-10-10T00:01:09.1026123Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:01:09.1026800Z """ 2025-10-10T00:01:09.1027301Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:09.1028003Z return parse_settings_from_text(settings_text) 2025-10-10T00:01:09.1028373Z 2025-10-10T00:01:09.1028379Z 2025-10-10T00:01:09.1028611Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:01:09.1029152Z """ 2025-10-10T00:01:09.1029601Z Parse users from the rollout state. 2025-10-10T00:01:09.1029926Z 2025-10-10T00:01:09.1030064Z """ 2025-10-10T00:01:09.1030537Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:01:09.1031201Z return parse_user_opt_in_from_text(users_text) 2025-10-10T00:01:09.1031573Z 2025-10-10T00:01:09.1031579Z 2025-10-10T00:01:09.1032087Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:09.1032787Z """ 2025-10-10T00:01:09.1033148Z Check if a user is opted into an experiment 2025-10-10T00:01:09.1033635Z """ 2025-10-10T00:01:09.1034035Z return experiment_name in user_optins.get(user, []) 2025-10-10T00:01:09.1034426Z 2025-10-10T00:01:09.1034437Z 2025-10-10T00:01:09.1034826Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:01:09.1035510Z """ 2025-10-10T00:01:09.1035920Z Check if a user explicitly opted out of an experiment 2025-10-10T00:01:09.1036452Z """ 2025-10-10T00:01:09.1036904Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:01:09.1037528Z experiment_optout = "-" + experiment_name 2025-10-10T00:01:09.1038108Z if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:01:09.1038652Z return False 2025-10-10T00:01:09.1038878Z 2025-10-10T00:01:09.1039124Z if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:01:09.1039778Z log.warning( 2025-10-10T00:01:09.1040514Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:01:09.1041327Z ) 2025-10-10T00:01:09.1041510Z 2025-10-10T00:01:09.1041666Z return True 2025-10-10T00:01:09.1042011Z 2025-10-10T00:01:09.1042018Z 2025-10-10T00:01:09.1042177Z def get_runner_prefix( 2025-10-10T00:01:09.1042570Z rollout_state: str, 2025-10-10T00:01:09.1042979Z workflow_requestors: Iterable[str], 2025-10-10T00:01:09.1043447Z branch: str, 2025-10-10T00:01:09.1043878Z eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:09.1044498Z opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:01:09.1045038Z is_canary: bool = False, 2025-10-10T00:01:09.1045438Z ) -> str: 2025-10-10T00:01:09.1045812Z settings = parse_settings(rollout_state) 2025-10-10T00:01:09.1046339Z user_optins = parse_users(rollout_state) 2025-10-10T00:01:09.1046673Z 2025-10-10T00:01:09.1046835Z fleet_prefix = "" 2025-10-10T00:01:09.1047210Z prefixes = [] 2025-10-10T00:01:09.1047780Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:01:09.1048641Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:01:09.1049300Z log.info( 2025-10-10T00:01:09.1050018Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:01:09.1050707Z ) 2025-10-10T00:01:09.1051052Z continue 2025-10-10T00:01:09.1051270Z 2025-10-10T00:01:09.1051435Z if opt_out_experiments: 2025-10-10T00:01:09.1051923Z if experiment_name in opt_out_experiments: 2025-10-10T00:01:09.1052496Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:01:09.1053035Z log.info( 2025-10-10T00:01:09.1053892Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:01:09.1054798Z ) 2025-10-10T00:01:09.1055173Z continue 2025-10-10T00:01:09.1055410Z 2025-10-10T00:01:09.1055579Z if eligible_experiments: 2025-10-10T00:01:09.1056085Z if experiment_name not in eligible_experiments: 2025-10-10T00:01:09.1056666Z exp_list = ", ".join(eligible_experiments) 2025-10-10T00:01:09.1057181Z log.info( 2025-10-10T00:01:09.1057900Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:01:09.1058677Z ) 2025-10-10T00:01:09.1059032Z continue 2025-10-10T00:01:09.1059538Z elif not experiment_settings.default: 2025-10-10T00:01:09.1060033Z log.info( 2025-10-10T00:01:09.1060750Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:01:09.1061440Z ) 2025-10-10T00:01:09.1061765Z continue 2025-10-10T00:01:09.1061993Z 2025-10-10T00:01:09.1062248Z # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:01:09.1062809Z opted_out_users = [ 2025-10-10T00:01:09.1063204Z requestor 2025-10-10T00:01:09.1063617Z for requestor in workflow_requestors 2025-10-10T00:01:09.1064227Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:01:09.1064800Z ] 2025-10-10T00:01:09.1064977Z 2025-10-10T00:01:09.1065144Z if opted_out_users: 2025-10-10T00:01:09.1065547Z log.info( 2025-10-10T00:01:09.1066103Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:01:09.1066740Z ) 2025-10-10T00:01:09.1067080Z continue 2025-10-10T00:01:09.1067298Z 2025-10-10T00:01:09.1067552Z # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:01:09.1068114Z opted_in_users = [ 2025-10-10T00:01:09.1068510Z requestor 2025-10-10T00:01:09.1068924Z for requestor in workflow_requestors 2025-10-10T00:01:09.1069664Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:01:09.1070233Z ] 2025-10-10T00:01:09.1070542Z 2025-10-10T00:01:09.1070694Z enabled = False 2025-10-10T00:01:09.1071097Z if opted_in_users: 2025-10-10T00:01:09.1071495Z log.info( 2025-10-10T00:01:09.1072035Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:01:09.1072664Z ) 2025-10-10T00:01:09.1073005Z enabled = True 2025-10-10T00:01:09.1073260Z 2025-10-10T00:01:09.1073456Z elif experiment_settings.rollout_perc: 2025-10-10T00:01:09.1074221Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:01:09.1075090Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:01:09.1075690Z log.info( 2025-10-10T00:01:09.1076480Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:01:09.1077330Z ) 2025-10-10T00:01:09.1077687Z enabled = True 2025-10-10T00:01:09.1077963Z 2025-10-10T00:01:09.1078111Z if enabled: 2025-10-10T00:01:09.1078485Z label = experiment_name 2025-10-10T00:01:09.1078990Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:01:09.1079849Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:01:09.1080657Z # - If it's enabled, then we always list it's prefix first 2025-10-10T00:01:09.1081353Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:01:09.1081951Z if is_canary: 2025-10-10T00:01:09.1082401Z label += CANARY_FLEET_SUFFIX 2025-10-10T00:01:09.1082900Z fleet_prefix = label 2025-10-10T00:01:09.1083344Z else: 2025-10-10T00:01:09.1083716Z prefixes.append(label) 2025-10-10T00:01:09.1084041Z 2025-10-10T00:01:09.1084204Z if len(prefixes) > 1: 2025-10-10T00:01:09.1084616Z log.error( 2025-10-10T00:01:09.1085566Z 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-10-10T00:01:09.1086614Z ) 2025-10-10T00:01:09.1086957Z prefixes = prefixes[:1] 2025-10-10T00:01:09.1087243Z 2025-10-10T00:01:09.1087414Z # Fleet always comes first 2025-10-10T00:01:09.1087831Z if fleet_prefix: 2025-10-10T00:01:09.1088241Z prefixes.insert(0, fleet_prefix) 2025-10-10T00:01:09.1088568Z 2025-10-10T00:01:09.1088979Z return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:01:09.1089361Z 2025-10-10T00:01:09.1089368Z 2025-10-10T00:01:09.1089877Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:01:09.1090600Z """ 2025-10-10T00:01:09.1091128Z Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:01:09.1091666Z 2025-10-10T00:01:09.1092022Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:01:09.1092664Z """ 2025-10-10T00:01:09.1093010Z gh = get_gh_client(github_token) 2025-10-10T00:01:09.1093513Z issue = get_issue(gh, repo, issue_num) 2025-10-10T00:01:09.1094091Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:01:09.1094500Z 2025-10-10T00:01:09.1094512Z 2025-10-10T00:01:09.1094887Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:01:09.1095585Z for _ in range(num_retries): 2025-10-10T00:01:09.1096016Z try: 2025-10-10T00:01:09.1096392Z req = Request(url=url, headers=headers) 2025-10-10T00:01:09.1096993Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:01:09.1097578Z return json.loads(content) 2025-10-10T00:01:09.1098052Z except Exception as e: 2025-10-10T00:01:09.1098543Z log.warning(f"Could not download {url}: {e}") 2025-10-10T00:01:09.1099042Z 2025-10-10T00:01:09.1099395Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:01:09.1100159Z return {} 2025-10-10T00:01:09.1100362Z 2025-10-10T00:01:09.1100368Z 2025-10-10T00:01:09.1100512Z @cache 2025-10-10T00:01:09.1101080Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:01:09.1101773Z """ 2025-10-10T00:01:09.1102123Z Dynamically get PR information 2025-10-10T00:01:09.1102567Z """ 2025-10-10T00:01:09.1103009Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:01:09.1103586Z headers = { 2025-10-10T00:01:09.1103991Z "Accept": "application/vnd.github.v3+json", 2025-10-10T00:01:09.1104540Z "Authorization": f"token {github_token}", 2025-10-10T00:01:09.1105019Z } 2025-10-10T00:01:09.1105406Z json_response: dict[str, Any] = download_json( 2025-10-10T00:01:09.1105967Z url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:01:09.1106460Z headers=headers, 2025-10-10T00:01:09.1106845Z ) 2025-10-10T00:01:09.1107021Z 2025-10-10T00:01:09.1107186Z if not json_response: 2025-10-10T00:01:09.1107703Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:01:09.1108269Z return {} 2025-10-10T00:01:09.1108487Z 2025-10-10T00:01:09.1108646Z return json_response 2025-10-10T00:01:09.1108896Z 2025-10-10T00:01:09.1108902Z 2025-10-10T00:01:09.1109267Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:01:09.1110072Z """ 2025-10-10T00:01:09.1110546Z Dynamically get the latest list of labels from the pull request 2025-10-10T00:01:09.1111138Z """ 2025-10-10T00:01:09.1111571Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:01:09.1112123Z return { 2025-10-10T00:01:09.1112643Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:01:09.1113281Z } 2025-10-10T00:01:09.1113465Z 2025-10-10T00:01:09.1113473Z 2025-10-10T00:01:09.1113633Z def main() -> None: 2025-10-10T00:01:09.1114010Z args = parse_args() 2025-10-10T00:01:09.1114251Z 2025-10-10T00:01:09.1114451Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:01:09.1114805Z 2025-10-10T00:01:09.1114983Z # Check if the PR is opt-out 2025-10-10T00:01:09.1115424Z if args.pr_number: 2025-10-10T00:01:09.1116024Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:01:09.1116833Z if OPT_OUT_LABEL in labels: 2025-10-10T00:01:09.1117299Z log.info( 2025-10-10T00:01:09.1117925Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:01:09.1118624Z ) 2025-10-10T00:01:09.1119122Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:09.1119837Z sys.exit() 2025-10-10T00:01:09.1120076Z 2025-10-10T00:01:09.1120226Z try: 2025-10-10T00:01:09.1120611Z rollout_state = get_rollout_state_from_issue( 2025-10-10T00:01:09.1121261Z args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:01:09.1121836Z ) 2025-10-10T00:01:09.1122024Z 2025-10-10T00:01:09.1122207Z username = get_potential_pr_author( 2025-10-10T00:01:09.1122703Z args.github_token, 2025-10-10T00:01:09.1123133Z args.github_repo, 2025-10-10T00:01:09.1123564Z args.github_actor, 2025-10-10T00:01:09.1123997Z args.github_ref_type, 2025-10-10T00:01:09.1124450Z args.github_branch, 2025-10-10T00:01:09.1124854Z ) 2025-10-10T00:01:09.1125039Z 2025-10-10T00:01:09.1125297Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:01:09.1125720Z 2025-10-10T00:01:09.1125917Z runner_label_prefix = get_runner_prefix( 2025-10-10T00:01:09.1126421Z rollout_state, 2025-10-10T00:01:09.1126992Z (args.github_issue_owner, username), 2025-10-10T00:01:09.1127493Z args.github_branch, 2025-10-10T00:01:09.1127944Z args.eligible_experiments, 2025-10-10T00:01:09.1128426Z args.opt_out_experiments, 2025-10-10T00:01:09.1128885Z is_canary, 2025-10-10T00:01:09.1129249Z ) 2025-10-10T00:01:09.1129439Z 2025-10-10T00:01:09.1129703Z except Exception as e: 2025-10-10T00:01:09.1130111Z log.error( 2025-10-10T00:01:09.1130729Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:01:09.1131442Z ) 2025-10-10T00:01:09.1131625Z 2025-10-10T00:01:09.1131928Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:01:09.1132391Z 2025-10-10T00:01:09.1132396Z 2025-10-10T00:01:09.1132567Z if __name__ == "__main__": 2025-10-10T00:01:09.1132955Z main() 2025-10-10T00:01:09.1133144Z 2025-10-10T00:01:09.1228627Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:01:09.1229677Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:01:09.1263125Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:09.1263569Z env: 2025-10-10T00:01:09.1264189Z GITHUB_TOKEN: *** 2025-10-10T00:01:09.1264568Z ISSUE_NUMBER: 5132 2025-10-10T00:01:09.1264977Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:09.1265431Z ISSUE_OWNER: 2025-10-10T00:01:09.1265802Z CHECK_EXPERIMENTS: 2025-10-10T00:01:09.1266193Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:01:09.1266589Z PR_NUMBER: 2025-10-10T00:01:09.1266925Z ##[endgroup] 2025-10-10T00:01:11.8014906Z Defaulting to user installation because normal site-packages is not writeable 2025-10-10T00:01:13.2201300Z Collecting urllib3==1.26.18 2025-10-10T00:01:13.2836052Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-10-10T00:01:13.3075241Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.2 MB/s eta 0:00:00 2025-10-10T00:01:13.3518886Z Collecting PyGithub==2.3.0 2025-10-10T00:01:13.3985243Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-10-10T00:01:13.4631577Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-10-10T00:01:13.4970196Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-10-10T00:01:13.5027153Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-10-10T00:01:13.5047253Z 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-10-10T00:01:13.5063614Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-10-10T00:01:13.5446064Z Collecting Deprecated (from PyGithub==2.3.0) 2025-10-10T00:01:13.5584953Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-10-10T00:01:13.5829922Z 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-10-10T00:01:13.7384147Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:01:13.7527705Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-10-10T00:01:13.8974311Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-10-10T00:01:13.9095961Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-10-10T00:01:13.9422348Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:01:13.9548584Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-10-10T00:01:13.9876765Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-10-10T00:01:14.0017397Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 11.4 MB/s eta 0:00:00 2025-10-10T00:01:14.0132383Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-10-10T00:01:14.0272770Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 28.8 MB/s eta 0:00:00 2025-10-10T00:01:14.0391617Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-10-10T00:01:14.0582900Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 81.2 MB/s eta 0:00:00 2025-10-10T00:01:14.0707968Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-10-10T00:01:14.0846219Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-10-10T00:01:14.0901781Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 56.9 MB/s eta 0:00:00 2025-10-10T00:01:14.1019906Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-10-10T00:01:14.1094111Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 13.9 MB/s eta 0:00:00 2025-10-10T00:01:14.1212285Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-10-10T00:01:14.1267957Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 31.2 MB/s eta 0:00:00 2025-10-10T00:01:14.5028361Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-10-10T00:01:15.0822023Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.0 urllib3-1.26.18 wrapt-1.17.3 2025-10-10T00:01:15.1889843Z ##[group]Run curr_branch="main" 2025-10-10T00:01:15.1890204Z curr_branch="main" 2025-10-10T00:01:15.1890447Z curr_ref_type="branch" 2025-10-10T00:01:15.1890699Z echo "Current branch is '$curr_branch'" 2025-10-10T00:01:15.1890967Z  2025-10-10T00:01:15.1891151Z python3 runner_determinator.py \ 2025-10-10T00:01:15.1891442Z  --github-token "$GITHUB_TOKEN" \ 2025-10-10T00:01:15.1891723Z  --github-issue "$ISSUE_NUMBER" \ 2025-10-10T00:01:15.1891980Z  --github-branch "$curr_branch" \ 2025-10-10T00:01:15.1892256Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-10-10T00:01:15.1892566Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-10-10T00:01:15.1892854Z  --github-ref-type "$curr_ref_type" \ 2025-10-10T00:01:15.1893119Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-10-10T00:01:15.1893423Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-10-10T00:01:15.1893791Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-10-10T00:01:15.1894089Z  --pr-number "${PR_NUMBER}" 2025-10-10T00:01:15.1927159Z shell: /usr/bin/bash -e {0} 2025-10-10T00:01:15.1927404Z env: 2025-10-10T00:01:15.1927949Z GITHUB_TOKEN: *** 2025-10-10T00:01:15.1928143Z ISSUE_NUMBER: 5132 2025-10-10T00:01:15.1928351Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:01:15.1928590Z ISSUE_OWNER: 2025-10-10T00:01:15.1928776Z CHECK_EXPERIMENTS: 2025-10-10T00:01:15.1928965Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:01:15.1929163Z PR_NUMBER: 2025-10-10T00:01:15.1929331Z ##[endgroup] 2025-10-10T00:01:15.1984365Z Current branch is 'main' 2025-10-10T00:01:17.4033440Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-10-10T00:01:17.4035066Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-10-10T00:01:17.4036329Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-10-10T00:01:17.4037154Z INFO : Setting output: label-type='' 2025-10-10T00:01:17.4406818Z Evaluate and set job outputs 2025-10-10T00:01:17.4414643Z Cleaning up orphan processes