2025-10-10T00:06:26.4870935Z Current runner version: '2.328.0' 2025-10-10T00:06:26.4894159Z ##[group]Runner Image Provisioner 2025-10-10T00:06:26.4894942Z Hosted Compute Agent 2025-10-10T00:06:26.4895659Z Version: 20250912.392 2025-10-10T00:06:26.4896308Z Commit: d921fda672a98b64f4f82364647e2f10b2267d0b 2025-10-10T00:06:26.4897031Z Build Date: 2025-09-12T15:23:14Z 2025-10-10T00:06:26.4897627Z ##[endgroup] 2025-10-10T00:06:26.4898193Z ##[group]Operating System 2025-10-10T00:06:26.4898770Z Ubuntu 2025-10-10T00:06:26.4899223Z 24.04.3 2025-10-10T00:06:26.4899735Z LTS 2025-10-10T00:06:26.4900182Z ##[endgroup] 2025-10-10T00:06:26.4900675Z ##[group]Runner Image 2025-10-10T00:06:26.4901207Z Image: ubuntu-24.04 2025-10-10T00:06:26.4901797Z Version: 20250929.60.1 2025-10-10T00:06:26.4902789Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250929.60/images/ubuntu/Ubuntu2404-Readme.md 2025-10-10T00:06:26.4904339Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250929.60 2025-10-10T00:06:26.4905576Z ##[endgroup] 2025-10-10T00:06:26.4907004Z ##[group]GITHUB_TOKEN Permissions 2025-10-10T00:06:26.4909127Z Contents: read 2025-10-10T00:06:26.4909690Z Metadata: read 2025-10-10T00:06:26.4910272Z ##[endgroup] 2025-10-10T00:06:26.4912567Z Secret source: Actions 2025-10-10T00:06:26.4913440Z Prepare workflow directory 2025-10-10T00:06:26.5501658Z Prepare all required actions 2025-10-10T00:06:26.5557339Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (344e6365a0068c2d2847fcec0c55dd53291d475e) 2025-10-10T00:06:26.5562418Z ##[group] Inputs 2025-10-10T00:06:26.5563025Z check_experiments: 2025-10-10T00:06:26.5563574Z opt_out_experiments: 2025-10-10T00:06:26.5564253Z triggering_actor: pytorchmergebot 2025-10-10T00:06:26.5564837Z issue_owner: 2025-10-10T00:06:26.5565592Z curr_branch: main 2025-10-10T00:06:26.5566177Z curr_ref_type: branch 2025-10-10T00:06:26.5566851Z issue_number: 5132 2025-10-10T00:06:26.5567409Z ##[endgroup] 2025-10-10T00:06:26.5568142Z Complete job name: before-test / get-label-type / runner-determinator 2025-10-10T00:06:27.2247267Z ##[group]Run cat < runner_determinator.py 2025-10-10T00:06:27.2249666Z cat < runner_determinator.py 2025-10-10T00:06:27.2250376Z # flake8: noqa: G004 2025-10-10T00:06:27.2250911Z  2025-10-10T00:06:27.2251858Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:06:27.2252920Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:06:27.2253904Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:06:27.2254714Z  2025-10-10T00:06:27.2255307Z """ 2025-10-10T00:06:27.2256049Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:06:27.2257124Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:06:27.2258348Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:06:27.2259377Z of which runners should be used to run which job. 2025-10-10T00:06:27.2260103Z  2025-10-10T00:06:27.2260810Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:06:27.2262061Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:06:27.2263126Z settings are considered to be empty with only the second part, the user 2025-10-10T00:06:27.2263970Z list, defined. 2025-10-10T00:06:27.2264540Z  2025-10-10T00:06:27.2265482Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:06:27.2266645Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:06:27.2267702Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:06:27.2268429Z  2025-10-10T00:06:27.2269560Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:06:27.2270554Z The user list is also a comma separated list of additional features or 2025-10-10T00:06:27.2271548Z experiments which the user could be opted in to. 2025-10-10T00:06:27.2272231Z  2025-10-10T00:06:27.2272756Z The user list has the following rules: 2025-10-10T00:06:27.2273465Z  2025-10-10T00:06:27.2274142Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:06:27.2275341Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:06:27.2276389Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:06:27.2277086Z  2025-10-10T00:06:27.2277536Z Example config: 2025-10-10T00:06:27.2364213Z  # A list of experiments that can be opted into. 2025-10-10T00:06:27.2366010Z  # This defines the behavior they'll induce when opted into. 2025-10-10T00:06:27.2367389Z  # Expected syntax is: 2025-10-10T00:06:27.2368819Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:06:27.2370213Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:06:27.2371018Z  2025-10-10T00:06:27.2371416Z  experiments: 2025-10-10T00:06:27.2371870Z  lf: 2025-10-10T00:06:27.2372314Z  rollout_percent: 25 2025-10-10T00:06:27.2372842Z  all_branches: false 2025-10-10T00:06:27.2373359Z  default: true 2025-10-10T00:06:27.2373830Z  --- 2025-10-10T00:06:27.2374230Z  2025-10-10T00:06:27.2374622Z  # Opt-ins: 2025-10-10T00:06:27.2375524Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:06:27.2376724Z  # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:06:27.2377588Z  # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:06:27.2378323Z  # Experiments should be from the above list. 2025-10-10T00:06:27.2378915Z  2025-10-10T00:06:27.2379332Z  @User1,-lf,split_build 2025-10-10T00:06:27.2379851Z  @User2,lf 2025-10-10T00:06:27.2380304Z  @User3,split_build 2025-10-10T00:06:27.2380788Z """ 2025-10-10T00:06:27.2381171Z  2025-10-10T00:06:27.2381566Z import json 2025-10-10T00:06:27.2382002Z import logging 2025-10-10T00:06:27.2382448Z import os 2025-10-10T00:06:27.2382879Z import random 2025-10-10T00:06:27.2383318Z import re 2025-10-10T00:06:27.2383750Z import sys 2025-10-10T00:06:27.2384236Z from argparse import ArgumentParser 2025-10-10T00:06:27.2384902Z from collections.abc import Iterable 2025-10-10T00:06:27.2385696Z from functools import cache 2025-10-10T00:06:27.2386245Z from logging import LogRecord 2025-10-10T00:06:27.2386817Z from typing import Any, NamedTuple 2025-10-10T00:06:27.2387442Z from urllib.request import Request, urlopen 2025-10-10T00:06:27.2388031Z  2025-10-10T00:06:27.2388408Z import yaml 2025-10-10T00:06:27.2388875Z from github import Auth, Github 2025-10-10T00:06:27.2389430Z from github.Issue import Issue 2025-10-10T00:06:27.2389941Z  2025-10-10T00:06:27.2390315Z  2025-10-10T00:06:27.2390773Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:06:27.2391539Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:06:27.2392508Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:06:27.2393275Z  2025-10-10T00:06:27.2393939Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:06:27.2394591Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:06:27.2395439Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:06:27.2396106Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:06:27.2396685Z  2025-10-10T00:06:27.2397120Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:06:27.2397673Z  2025-10-10T00:06:27.2398076Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:06:27.2398619Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:06:27.2399113Z  2025-10-10T00:06:27.2399489Z  2025-10-10T00:06:27.2399938Z class Experiment(NamedTuple): 2025-10-10T00:06:27.2400491Z  rollout_perc: float = ( 2025-10-10T00:06:27.2401230Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:06:27.2401964Z  ) 2025-10-10T00:06:27.2402390Z  all_branches: bool = ( 2025-10-10T00:06:27.2403154Z  False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:06:27.2403880Z  ) 2025-10-10T00:06:27.2404320Z  default: bool = ( 2025-10-10T00:06:27.2404979Z  True # If True, the experiment is enabled by default for all queries 2025-10-10T00:06:27.2406082Z  ) 2025-10-10T00:06:27.2406477Z  2025-10-10T00:06:27.2406883Z  # Add more fields as needed 2025-10-10T00:06:27.2407401Z  2025-10-10T00:06:27.2407782Z  2025-10-10T00:06:27.2408189Z class Settings(NamedTuple): 2025-10-10T00:06:27.2408700Z  """ 2025-10-10T00:06:27.2409231Z  Settings for the experiments that can be opted into. 2025-10-10T00:06:27.2409855Z  """ 2025-10-10T00:06:27.2410255Z  2025-10-10T00:06:27.2410696Z  experiments: dict[str, Experiment] = {} 2025-10-10T00:06:27.2411269Z  2025-10-10T00:06:27.2411795Z  2025-10-10T00:06:27.2412262Z class ColorFormatter(logging.Formatter): 2025-10-10T00:06:27.2412972Z  """Color codes the log messages based on the log level""" 2025-10-10T00:06:27.2413605Z  2025-10-10T00:06:27.2413989Z  COLORS = { 2025-10-10T00:06:27.2414515Z  "WARNING": "\033[33m", # Yellow 2025-10-10T00:06:27.2415306Z  "ERROR": "\033[31m", # Red 2025-10-10T00:06:27.2415883Z  "CRITICAL": "\033[31m", # Red 2025-10-10T00:06:27.2416450Z  "INFO": "\033[0m", # Reset 2025-10-10T00:06:27.2416997Z  "DEBUG": "\033[0m", # Reset 2025-10-10T00:06:27.2417525Z  } 2025-10-10T00:06:27.2417916Z  2025-10-10T00:06:27.2418384Z  def format(self, record: LogRecord) -> str: 2025-10-10T00:06:27.2419206Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:06:27.2420047Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:06:27.2420685Z  return super().format(record) 2025-10-10T00:06:27.2421214Z  2025-10-10T00:06:27.2421591Z  2025-10-10T00:06:27.2422007Z handler = logging.StreamHandler() 2025-10-10T00:06:27.2422807Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:06:27.2423567Z  2025-10-10T00:06:27.2424069Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:06:27.2424723Z log.addHandler(handler) 2025-10-10T00:06:27.2425408Z log.setLevel(logging.INFO) 2025-10-10T00:06:27.2425921Z  2025-10-10T00:06:27.2426291Z  2025-10-10T00:06:27.2426789Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:06:27.2427411Z  """ 2025-10-10T00:06:27.2427993Z  Defines outputs of the github action that invokes this script 2025-10-10T00:06:27.2428839Z  """ 2025-10-10T00:06:27.2429267Z  if not GITHUB_OUTPUT: 2025-10-10T00:06:27.2430430Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:06:27.2431610Z  log.warning( 2025-10-10T00:06:27.2432557Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:06:27.2433544Z  ) 2025-10-10T00:06:27.2434041Z  print(f"::set-output name={key}::{value}") 2025-10-10T00:06:27.2434633Z  return 2025-10-10T00:06:27.2435280Z  2025-10-10T00:06:27.2435752Z  with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:06:27.2436439Z  log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:06:27.2437062Z  f.write(f"{key}={value}\n") 2025-10-10T00:06:27.2437599Z  2025-10-10T00:06:27.2437984Z  2025-10-10T00:06:27.2438534Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:06:27.2439242Z  return frozenset( 2025-10-10T00:06:27.2439942Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:06:27.2440668Z  ) 2025-10-10T00:06:27.2441069Z  2025-10-10T00:06:27.2441438Z  2025-10-10T00:06:27.2441839Z def parse_args() -> Any: 2025-10-10T00:06:27.2442501Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:06:27.2443436Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:06:27.2444255Z  parser.add_argument( 2025-10-10T00:06:27.2444785Z  "--github-issue-repo", 2025-10-10T00:06:27.2445532Z  type=str, 2025-10-10T00:06:27.2446039Z  required=False, 2025-10-10T00:06:27.2446707Z  default="pytorch/test-infra", 2025-10-10T00:06:27.2447328Z  help="GitHub repo to get the issue", 2025-10-10T00:06:27.2447884Z  ) 2025-10-10T00:06:27.2448319Z  parser.add_argument( 2025-10-10T00:06:27.2448830Z  "--github-repo", 2025-10-10T00:06:27.2449331Z  type=str, 2025-10-10T00:06:27.2449801Z  required=True, 2025-10-10T00:06:27.2450344Z  help="GitHub repo where CI is running", 2025-10-10T00:06:27.2450903Z  ) 2025-10-10T00:06:27.2451366Z  parser.add_argument( 2025-10-10T00:06:27.2452058Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:06:27.2452755Z  ) 2025-10-10T00:06:27.2453179Z  parser.add_argument( 2025-10-10T00:06:27.2453901Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:06:27.2454639Z  ) 2025-10-10T00:06:27.2455216Z  parser.add_argument( 2025-10-10T00:06:27.2455966Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:06:27.2456699Z  ) 2025-10-10T00:06:27.2457126Z  parser.add_argument( 2025-10-10T00:06:27.2457879Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:06:27.2458632Z  ) 2025-10-10T00:06:27.2459065Z  parser.add_argument( 2025-10-10T00:06:27.2459584Z  "--github-ref-type", 2025-10-10T00:06:27.2460107Z  type=str, 2025-10-10T00:06:27.2460576Z  required=True, 2025-10-10T00:06:27.2461164Z  help="Current GitHub ref type, branch or tag", 2025-10-10T00:06:27.2461752Z  ) 2025-10-10T00:06:27.2462173Z  parser.add_argument( 2025-10-10T00:06:27.2462847Z  "--eligible-experiments", 2025-10-10T00:06:27.2463448Z  type=_str_comma_separated_to_set, 2025-10-10T00:06:27.2464014Z  required=False, 2025-10-10T00:06:27.2464510Z  default="", 2025-10-10T00:06:27.2465633Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:06:27.2466612Z  ) 2025-10-10T00:06:27.2467037Z  parser.add_argument( 2025-10-10T00:06:27.2467565Z  "--opt-out-experiments", 2025-10-10T00:06:27.2468147Z  type=_str_comma_separated_to_set, 2025-10-10T00:06:27.2468709Z  required=False, 2025-10-10T00:06:27.2469200Z  default="", 2025-10-10T00:06:27.2469667Z  help=( 2025-10-10T00:06:27.2470437Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:06:27.2471647Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:06:27.2472517Z  ), 2025-10-10T00:06:27.2472934Z  ) 2025-10-10T00:06:27.2473347Z  parser.add_argument( 2025-10-10T00:06:27.2473871Z  "--pr-number", 2025-10-10T00:06:27.2474357Z  type=str, 2025-10-10T00:06:27.2474832Z  required=False, 2025-10-10T00:06:27.2475568Z  default="", 2025-10-10T00:06:27.2476150Z  help="the optional PR number where this is run", 2025-10-10T00:06:27.2476763Z  ) 2025-10-10T00:06:27.2477155Z  2025-10-10T00:06:27.2477572Z  return parser.parse_args() 2025-10-10T00:06:27.2478091Z  2025-10-10T00:06:27.2478460Z  2025-10-10T00:06:27.2479097Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:06:27.2480072Z  auth = Auth.Token(github_token) 2025-10-10T00:06:27.2480682Z  return Github(auth=auth) 2025-10-10T00:06:27.2481195Z  2025-10-10T00:06:27.2481564Z  2025-10-10T00:06:27.2482273Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:06:27.2483133Z  repo = gh.get_repo(repo) 2025-10-10T00:06:27.2483726Z  return repo.get_issue(number=issue_num) 2025-10-10T00:06:27.2484295Z  2025-10-10T00:06:27.2484672Z  2025-10-10T00:06:27.2485779Z def get_potential_pr_author( 2025-10-10T00:06:27.2486564Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:06:27.2487292Z ) -> str: 2025-10-10T00:06:27.2487892Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:06:27.2488770Z  # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:06:27.2489599Z  # embedded in the tag name: ciflow// 2025-10-10T00:06:27.2490233Z  2025-10-10T00:06:27.2490653Z  gh = get_gh_client(github_token) 2025-10-10T00:06:27.2491186Z  2025-10-10T00:06:27.2491695Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:06:27.2492399Z  split_tag = ref_name.split("/") 2025-10-10T00:06:27.2492942Z  if ( 2025-10-10T00:06:27.2493391Z  len(split_tag) == 3 2025-10-10T00:06:27.2493954Z  and split_tag[0] == "ciflow" 2025-10-10T00:06:27.2494535Z  and split_tag[2].isnumeric() 2025-10-10T00:06:27.2495250Z  ): 2025-10-10T00:06:27.2495724Z  pr_number = split_tag[2] 2025-10-10T00:06:27.2496267Z  try: 2025-10-10T00:06:27.2496771Z  repository = gh.get_repo(repo) 2025-10-10T00:06:27.2497619Z  pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:06:27.2498289Z  except Exception as e: 2025-10-10T00:06:27.2498870Z  raise Exception( # noqa: TRY002 2025-10-10T00:06:27.2499600Z  f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:06:27.2500284Z  ) from e 2025-10-10T00:06:27.2500914Z  return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:06:27.2501673Z  # In all other cases, return the original input username 2025-10-10T00:06:27.2502312Z  return username 2025-10-10T00:06:27.2502770Z  2025-10-10T00:06:27.2503132Z  2025-10-10T00:06:27.2503604Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:06:27.2504179Z  """ 2025-10-10T00:06:27.2504897Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:06:27.2505923Z  """ 2025-10-10T00:06:27.2506538Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:06:27.2507253Z  2025-10-10T00:06:27.2507619Z  2025-10-10T00:06:27.2508046Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:06:27.2508589Z  try: 2025-10-10T00:06:27.2509045Z  data = yaml.safe_load(yaml_text) 2025-10-10T00:06:27.2509600Z  return data 2025-10-10T00:06:27.2510099Z  except yaml.YAMLError: 2025-10-10T00:06:27.2510658Z  log.exception("Error loading YAML") 2025-10-10T00:06:27.2511219Z  raise 2025-10-10T00:06:27.2511642Z  2025-10-10T00:06:27.2512009Z  2025-10-10T00:06:27.2512669Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:06:27.2513451Z  """ 2025-10-10T00:06:27.2514275Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:06:27.2515278Z  2025-10-10T00:06:27.2515892Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:27.2516764Z  and the text below is the list of opted in users. 2025-10-10T00:06:27.2517362Z  2025-10-10T00:06:27.2517979Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:06:27.2518722Z  """ 2025-10-10T00:06:27.2519356Z  rollout_state_parts = rollout_state.split("---") 2025-10-10T00:06:27.2520144Z  if len(rollout_state_parts) >= 2: 2025-10-10T00:06:27.2520827Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:06:27.2521462Z  else: 2025-10-10T00:06:27.2521901Z  return "", rollout_state 2025-10-10T00:06:27.2522420Z  2025-10-10T00:06:27.2522778Z  2025-10-10T00:06:27.2523221Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:06:27.2523767Z  """ 2025-10-10T00:06:27.2524349Z  Dictionary of users with a list of features they have opted into 2025-10-10T00:06:27.2525207Z  """ 2025-10-10T00:06:27.2525632Z  2025-10-10T00:06:27.2525993Z  2025-10-10T00:06:27.2526574Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:06:27.2527275Z  """ 2025-10-10T00:06:27.2528055Z  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:06:27.2528932Z  2025-10-10T00:06:27.2529787Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:06:27.2530834Z  - Example line: "@User1,lf,split_build" 2025-10-10T00:06:27.2531736Z  - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:06:27.2532408Z  2025-10-10T00:06:27.2532772Z  2025-10-10T00:06:27.2533130Z  """ 2025-10-10T00:06:27.2533548Z  optins = UserOptins() 2025-10-10T00:06:27.2534102Z  for user in user_optin_text.split("\n"): 2025-10-10T00:06:27.2534708Z  user = user.strip("\r\n\t -") 2025-10-10T00:06:27.2535503Z  if not user or not user.startswith("@"): 2025-10-10T00:06:27.2536109Z  # Not a valid user. Skip 2025-10-10T00:06:27.2536681Z  continue 2025-10-10T00:06:27.2537128Z  2025-10-10T00:06:27.2537502Z  if user: 2025-10-10T00:06:27.2538011Z  usr_name = user.split(",")[0].strip("@") 2025-10-10T00:06:27.2538760Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:06:27.2539433Z  2025-10-10T00:06:27.2539818Z  return optins 2025-10-10T00:06:27.2540263Z  2025-10-10T00:06:27.2540618Z  2025-10-10T00:06:27.2541147Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:06:27.2541793Z  """ 2025-10-10T00:06:27.2542243Z  Check if the experiment name is valid. 2025-10-10T00:06:27.2542800Z  A valid name: 2025-10-10T00:06:27.2543517Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:06:27.2544508Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:06:27.2545433Z  - Cannot contain spaces 2025-10-10T00:06:27.2545951Z  """ 2025-10-10T00:06:27.2546333Z  2025-10-10T00:06:27.2546832Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:06:27.2547767Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:06:27.2548621Z  2025-10-10T00:06:27.2549003Z  if valid: 2025-10-10T00:06:27.2549443Z  return True 2025-10-10T00:06:27.2549894Z  2025-10-10T00:06:27.2550264Z  log.error( 2025-10-10T00:06:27.2551871Z  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:06:27.2553472Z  ) 2025-10-10T00:06:27.2553875Z  return False 2025-10-10T00:06:27.2554309Z  2025-10-10T00:06:27.2554660Z  2025-10-10T00:06:27.2555425Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:06:27.2556117Z  """ 2025-10-10T00:06:27.2556785Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:06:27.2557546Z  """ 2025-10-10T00:06:27.2557940Z  try: 2025-10-10T00:06:27.2558347Z  if settings_text: 2025-10-10T00:06:27.2559151Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:06:27.2560005Z  # for easy reading 2025-10-10T00:06:27.2560865Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:06:27.2561805Z  # the backtick character in shell commands. 2025-10-10T00:06:27.2562459Z  backtick = chr(96) # backtick character 2025-10-10T00:06:27.2563182Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:06:27.2563891Z  settings = load_yaml(settings_text) 2025-10-10T00:06:27.2564429Z  2025-10-10T00:06:27.2565297Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:06:27.2566317Z  experiments = {} 2025-10-10T00:06:27.2566821Z  2025-10-10T00:06:27.2567422Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:06:27.2568232Z  if not is_valid_experiment_name(exp_name): 2025-10-10T00:06:27.2569392Z  # 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:06:27.2570465Z  continue 2025-10-10T00:06:27.2570959Z  2025-10-10T00:06:27.2571361Z  valid_settings = {} 2025-10-10T00:06:27.2571937Z  for setting in exp_settings: 2025-10-10T00:06:27.2572559Z  if setting not in Experiment._fields: 2025-10-10T00:06:27.2573171Z  log.warning( 2025-10-10T00:06:27.2573944Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:06:27.2574688Z  ) 2025-10-10T00:06:27.2575305Z  else: 2025-10-10T00:06:27.2575900Z  valid_settings[setting] = exp_settings[setting] 2025-10-10T00:06:27.2576544Z  2025-10-10T00:06:27.2577056Z  experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:06:27.2577743Z  return Settings(experiments) 2025-10-10T00:06:27.2578282Z  2025-10-10T00:06:27.2578667Z  except Exception: 2025-10-10T00:06:27.2579225Z  log.exception("Failed to parse settings") 2025-10-10T00:06:27.2579794Z  2025-10-10T00:06:27.2580182Z  return Settings() 2025-10-10T00:06:27.2580652Z  2025-10-10T00:06:27.2581014Z  2025-10-10T00:06:27.2581635Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:06:27.2582263Z  """ 2025-10-10T00:06:27.2582765Z  Parse settings, if any, from the rollout state. 2025-10-10T00:06:27.2583355Z  2025-10-10T00:06:27.2583932Z  If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:27.2584745Z  and the text below is the list of opted in users. 2025-10-10T00:06:27.2585440Z  2025-10-10T00:06:27.2586075Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:06:27.2586837Z  """ 2025-10-10T00:06:27.2587455Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:27.2588288Z  return parse_settings_from_text(settings_text) 2025-10-10T00:06:27.2588874Z  2025-10-10T00:06:27.2589229Z  2025-10-10T00:06:27.2589729Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:06:27.2590339Z  """ 2025-10-10T00:06:27.2590785Z  Parse users from the rollout state. 2025-10-10T00:06:27.2591336Z  2025-10-10T00:06:27.2591695Z  """ 2025-10-10T00:06:27.2592293Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:27.2593096Z  return parse_user_opt_in_from_text(users_text) 2025-10-10T00:06:27.2593683Z  2025-10-10T00:06:27.2594039Z  2025-10-10T00:06:27.2594715Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:27.2595606Z  """ 2025-10-10T00:06:27.2596086Z  Check if a user is opted into an experiment 2025-10-10T00:06:27.2596658Z  """ 2025-10-10T00:06:27.2597170Z  return experiment_name in user_optins.get(user, []) 2025-10-10T00:06:27.2597785Z  2025-10-10T00:06:27.2598269Z  2025-10-10T00:06:27.2598954Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:27.2599756Z  """ 2025-10-10T00:06:27.2600284Z  Check if a user explicitly opted out of an experiment 2025-10-10T00:06:27.2600908Z  """ 2025-10-10T00:06:27.2601469Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:06:27.2602225Z  experiment_optout = "-" + experiment_name 2025-10-10T00:06:27.2602926Z  if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:06:27.2603567Z  return False 2025-10-10T00:06:27.2604016Z  2025-10-10T00:06:27.2604511Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:06:27.2605252Z  log.warning( 2025-10-10T00:06:27.2606145Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:06:27.2607068Z  ) 2025-10-10T00:06:27.2607462Z  2025-10-10T00:06:27.2607834Z  return True 2025-10-10T00:06:27.2608267Z  2025-10-10T00:06:27.2608632Z  2025-10-10T00:06:27.2609033Z def get_runner_prefix( 2025-10-10T00:06:27.2609533Z  rollout_state: str, 2025-10-10T00:06:27.2610070Z  workflow_requestors: Iterable[str], 2025-10-10T00:06:27.2610624Z  branch: str, 2025-10-10T00:06:27.2611200Z  eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:27.2611932Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:27.2612565Z  is_canary: bool = False, 2025-10-10T00:06:27.2613071Z ) -> str: 2025-10-10T00:06:27.2613570Z  settings = parse_settings(rollout_state) 2025-10-10T00:06:27.2614214Z  user_optins = parse_users(rollout_state) 2025-10-10T00:06:27.2614777Z  2025-10-10T00:06:27.2615388Z  fleet_prefix = "" 2025-10-10T00:06:27.2615886Z  prefixes = [] 2025-10-10T00:06:27.2616597Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:06:27.2617592Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:06:27.2618346Z  log.info( 2025-10-10T00:06:27.2619091Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:06:27.2619875Z  ) 2025-10-10T00:06:27.2620322Z  continue 2025-10-10T00:06:27.2620770Z  2025-10-10T00:06:27.2621176Z  if opt_out_experiments: 2025-10-10T00:06:27.2621779Z  if experiment_name in opt_out_experiments: 2025-10-10T00:06:27.2622484Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:06:27.2623131Z  log.info( 2025-10-10T00:06:27.2624127Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:06:27.2625237Z  ) 2025-10-10T00:06:27.2625733Z  continue 2025-10-10T00:06:27.2626207Z  2025-10-10T00:06:27.2626611Z  if eligible_experiments: 2025-10-10T00:06:27.2627233Z  if experiment_name not in eligible_experiments: 2025-10-10T00:06:27.2627919Z  exp_list = ", ".join(eligible_experiments) 2025-10-10T00:06:27.2628510Z  log.info( 2025-10-10T00:06:27.2629362Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:06:27.2630230Z  ) 2025-10-10T00:06:27.2630813Z  continue 2025-10-10T00:06:27.2631379Z  elif not experiment_settings.default: 2025-10-10T00:06:27.2631957Z  log.info( 2025-10-10T00:06:27.2632692Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:06:27.2633464Z  ) 2025-10-10T00:06:27.2633908Z  continue 2025-10-10T00:06:27.2634354Z  2025-10-10T00:06:27.2634870Z  # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:06:27.2635641Z  opted_out_users = [ 2025-10-10T00:06:27.2636163Z  requestor 2025-10-10T00:06:27.2636706Z  for requestor in workflow_requestors 2025-10-10T00:06:27.2637436Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:06:27.2638110Z  ] 2025-10-10T00:06:27.2638512Z  2025-10-10T00:06:27.2638918Z  if opted_out_users: 2025-10-10T00:06:27.2639447Z  log.info( 2025-10-10T00:06:27.2640146Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:06:27.2640884Z  ) 2025-10-10T00:06:27.2641322Z  continue 2025-10-10T00:06:27.2641776Z  2025-10-10T00:06:27.2642274Z  # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:06:27.2642936Z  opted_in_users = [ 2025-10-10T00:06:27.2643463Z  requestor 2025-10-10T00:06:27.2644002Z  for requestor in workflow_requestors 2025-10-10T00:06:27.2644737Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:06:27.2645490Z  ] 2025-10-10T00:06:27.2645900Z  2025-10-10T00:06:27.2646284Z  enabled = False 2025-10-10T00:06:27.2646788Z  if opted_in_users: 2025-10-10T00:06:27.2647414Z  log.info( 2025-10-10T00:06:27.2648109Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:06:27.2648837Z  ) 2025-10-10T00:06:27.2649273Z  enabled = True 2025-10-10T00:06:27.2649754Z  2025-10-10T00:06:27.2650194Z  elif experiment_settings.rollout_perc: 2025-10-10T00:06:27.2651089Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:06:27.2652118Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:06:27.2652810Z  log.info( 2025-10-10T00:06:27.2653750Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:06:27.2654700Z  ) 2025-10-10T00:06:27.2655287Z  enabled = True 2025-10-10T00:06:27.2655808Z  2025-10-10T00:06:27.2656193Z  if enabled: 2025-10-10T00:06:27.2656692Z  label = experiment_name 2025-10-10T00:06:27.2657302Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:06:27.2658178Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:06:27.2659106Z  # - If it's enabled, then we always list it's prefix first 2025-10-10T00:06:27.2659953Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:06:27.2660667Z  if is_canary: 2025-10-10T00:06:27.2661226Z  label += CANARY_FLEET_SUFFIX 2025-10-10T00:06:27.2661821Z  fleet_prefix = label 2025-10-10T00:06:27.2662359Z  else: 2025-10-10T00:06:27.2662994Z  prefixes.append(label) 2025-10-10T00:06:27.2663536Z  2025-10-10T00:06:27.2663931Z  if len(prefixes) > 1: 2025-10-10T00:06:27.2664431Z  log.error( 2025-10-10T00:06:27.2665661Z  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:06:27.2666834Z  ) 2025-10-10T00:06:27.2667279Z  prefixes = prefixes[:1] 2025-10-10T00:06:27.2667804Z  2025-10-10T00:06:27.2668200Z  # Fleet always comes first 2025-10-10T00:06:27.2668737Z  if fleet_prefix: 2025-10-10T00:06:27.2669265Z  prefixes.insert(0, fleet_prefix) 2025-10-10T00:06:27.2669818Z  2025-10-10T00:06:27.2670308Z  return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:06:27.2670915Z  2025-10-10T00:06:27.2671290Z  2025-10-10T00:06:27.2671979Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:06:27.2672802Z  """ 2025-10-10T00:06:27.2673446Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:06:27.2674201Z  2025-10-10T00:06:27.2674823Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:06:27.2675664Z  """ 2025-10-10T00:06:27.2676118Z  gh = get_gh_client(github_token) 2025-10-10T00:06:27.2676724Z  issue = get_issue(gh, repo, issue_num) 2025-10-10T00:06:27.2677431Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:06:27.2678059Z  2025-10-10T00:06:27.2678420Z  2025-10-10T00:06:27.2679053Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:06:27.2679988Z  for _ in range(num_retries): 2025-10-10T00:06:27.2680524Z  try: 2025-10-10T00:06:27.2681013Z  req = Request(url=url, headers=headers) 2025-10-10T00:06:27.2681728Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:06:27.2682422Z  return json.loads(content) 2025-10-10T00:06:27.2682996Z  except Exception as e: 2025-10-10T00:06:27.2683609Z  log.warning(f"Could not download {url}: {e}") 2025-10-10T00:06:27.2684205Z  2025-10-10T00:06:27.2684823Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:06:27.2685673Z  return {} 2025-10-10T00:06:27.2686112Z  2025-10-10T00:06:27.2686471Z  2025-10-10T00:06:27.2686841Z @cache 2025-10-10T00:06:27.2687533Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:06:27.2688343Z  """ 2025-10-10T00:06:27.2688791Z  Dynamically get PR information 2025-10-10T00:06:27.2689328Z  """ 2025-10-10T00:06:27.2689889Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:06:27.2690554Z  headers = { 2025-10-10T00:06:27.2691092Z  "Accept": "application/vnd.github.v3+json", 2025-10-10T00:06:27.2691747Z  "Authorization": f"token {github_token}", 2025-10-10T00:06:27.2692317Z  } 2025-10-10T00:06:27.2692806Z  json_response: dict[str, Any] = download_json( 2025-10-10T00:06:27.2693464Z  url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:06:27.2694049Z  headers=headers, 2025-10-10T00:06:27.2694531Z  ) 2025-10-10T00:06:27.2694919Z  2025-10-10T00:06:27.2695409Z  if not json_response: 2025-10-10T00:06:27.2696111Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:06:27.2696917Z  return {} 2025-10-10T00:06:27.2697371Z  2025-10-10T00:06:27.2697760Z  return json_response 2025-10-10T00:06:27.2698234Z  2025-10-10T00:06:27.2698596Z  2025-10-10T00:06:27.2699247Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:06:27.2700028Z  """ 2025-10-10T00:06:27.2700608Z  Dynamically get the latest list of labels from the pull request 2025-10-10T00:06:27.2701305Z  """ 2025-10-10T00:06:27.2701841Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:06:27.2702489Z  return { 2025-10-10T00:06:27.2703139Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:06:27.2703862Z  } 2025-10-10T00:06:27.2704249Z  2025-10-10T00:06:27.2704613Z  2025-10-10T00:06:27.2705001Z def main() -> None: 2025-10-10T00:06:27.2705580Z  args = parse_args() 2025-10-10T00:06:27.2706070Z  2025-10-10T00:06:27.2706524Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:06:27.2707101Z  2025-10-10T00:06:27.2707501Z  # Check if the PR is opt-out 2025-10-10T00:06:27.2708039Z  if args.pr_number: 2025-10-10T00:06:27.2708775Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:06:27.2709571Z  if OPT_OUT_LABEL in labels: 2025-10-10T00:06:27.2710113Z  log.info( 2025-10-10T00:06:27.2710870Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:06:27.2711671Z  ) 2025-10-10T00:06:27.2712296Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:27.2713019Z  sys.exit() 2025-10-10T00:06:27.2713610Z  2025-10-10T00:06:27.2713981Z  try: 2025-10-10T00:06:27.2714473Z  rollout_state = get_rollout_state_from_issue( 2025-10-10T00:06:27.2715339Z  args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:06:27.2716022Z  ) 2025-10-10T00:06:27.2716428Z  2025-10-10T00:06:27.2716851Z  username = get_potential_pr_author( 2025-10-10T00:06:27.2717437Z  args.github_token, 2025-10-10T00:06:27.2717969Z  args.github_repo, 2025-10-10T00:06:27.2718504Z  args.github_actor, 2025-10-10T00:06:27.2719048Z  args.github_ref_type, 2025-10-10T00:06:27.2719600Z  args.github_branch, 2025-10-10T00:06:27.2720104Z  ) 2025-10-10T00:06:27.2720509Z  2025-10-10T00:06:27.2721038Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:06:27.2721685Z  2025-10-10T00:06:27.2722131Z  runner_label_prefix = get_runner_prefix( 2025-10-10T00:06:27.2722721Z  rollout_state, 2025-10-10T00:06:27.2723279Z  (args.github_issue_owner, username), 2025-10-10T00:06:27.2723868Z  args.github_branch, 2025-10-10T00:06:27.2724430Z  args.eligible_experiments, 2025-10-10T00:06:27.2725116Z  args.opt_out_experiments, 2025-10-10T00:06:27.2725668Z  is_canary, 2025-10-10T00:06:27.2726138Z  ) 2025-10-10T00:06:27.2726535Z  2025-10-10T00:06:27.2726933Z  except Exception as e: 2025-10-10T00:06:27.2727441Z  log.error( 2025-10-10T00:06:27.2728196Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:06:27.2729126Z  ) 2025-10-10T00:06:27.2729539Z  2025-10-10T00:06:27.2730109Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:27.2730802Z  2025-10-10T00:06:27.2731175Z  2025-10-10T00:06:27.2731561Z if __name__ == "__main__": 2025-10-10T00:06:27.2732064Z  main() 2025-10-10T00:06:27.2732471Z  2025-10-10T00:06:27.2732842Z EOF 2025-10-10T00:06:27.2733215Z  2025-10-10T00:06:27.2733619Z cat runner_determinator.py 2025-10-10T00:06:27.3833367Z shell: /usr/bin/bash -e {0} 2025-10-10T00:06:27.3834277Z env: 2025-10-10T00:06:27.3835290Z GITHUB_TOKEN: *** 2025-10-10T00:06:27.3835820Z ISSUE_NUMBER: 5132 2025-10-10T00:06:27.3836367Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:06:27.3836955Z ISSUE_OWNER: 2025-10-10T00:06:27.3837410Z CHECK_EXPERIMENTS: 2025-10-10T00:06:27.3837902Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:06:27.3838384Z PR_NUMBER: 2025-10-10T00:06:27.3838835Z ##[endgroup] 2025-10-10T00:06:27.4047040Z # flake8: noqa: G004 2025-10-10T00:06:27.4047422Z 2025-10-10T00:06:27.4047857Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-10-10T00:06:27.4048868Z # must be kept in sync. You can do it easily by running the following command: 2025-10-10T00:06:27.4049700Z # python .github/scripts/update_runner_determinator.py 2025-10-10T00:06:27.4050158Z 2025-10-10T00:06:27.4050326Z """ 2025-10-10T00:06:27.4050932Z This runner determinator is used to determine which set of runners to run a 2025-10-10T00:06:27.4051830Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-10-10T00:06:27.4052768Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-10-10T00:06:27.4053608Z of which runners should be used to run which job. 2025-10-10T00:06:27.4054045Z 2025-10-10T00:06:27.4054429Z The configuration has two parts, the settings and a list of opted-in users, 2025-10-10T00:06:27.4055797Z separated by a line containing "---". If the line is not present, the 2025-10-10T00:06:27.4056761Z settings are considered to be empty with only the second part, the user 2025-10-10T00:06:27.4057499Z list, defined. 2025-10-10T00:06:27.4057753Z 2025-10-10T00:06:27.4058129Z The first part is a YAML block that defines the rollout settings. This can be 2025-10-10T00:06:27.4059088Z used to define any settings that are needed to determine which runners to use. 2025-10-10T00:06:27.4059961Z It's fields are defined by the RolloutSettings class below. 2025-10-10T00:06:27.4060439Z 2025-10-10T00:06:27.4060813Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-10-10T00:06:27.4061709Z The user list is also a comma separated list of additional features or 2025-10-10T00:06:27.4062455Z experiments which the user could be opted in to. 2025-10-10T00:06:27.4062879Z 2025-10-10T00:06:27.4063089Z The user list has the following rules: 2025-10-10T00:06:27.4063468Z 2025-10-10T00:06:27.4063801Z - Users are GitHub usernames, which must start with the @ prefix 2025-10-10T00:06:27.4064692Z - Each user is also a comma-separated list of features/experiments to enable 2025-10-10T00:06:27.4065802Z - A "#" prefix opts the user out of all experiments 2025-10-10T00:06:27.4066241Z 2025-10-10T00:06:27.4066415Z Example config: 2025-10-10T00:06:27.4066912Z # A list of experiments that can be opted into. 2025-10-10T00:06:27.4067602Z # This defines the behavior they'll induce when opted into. 2025-10-10T00:06:27.4068254Z # Expected syntax is: 2025-10-10T00:06:27.4068920Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-10-10T00:06:27.4069918Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-10-10T00:06:27.4070542Z 2025-10-10T00:06:27.4070726Z experiments: 2025-10-10T00:06:27.4071153Z lf: 2025-10-10T00:06:27.4071566Z rollout_percent: 25 2025-10-10T00:06:27.4072239Z all_branches: false 2025-10-10T00:06:27.4072762Z default: true 2025-10-10T00:06:27.4073203Z --- 2025-10-10T00:06:27.4073444Z 2025-10-10T00:06:27.4073613Z # Opt-ins: 2025-10-10T00:06:27.4074227Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-10-10T00:06:27.4075607Z # and specifying experiments to enable in a comma-separated list. 2025-10-10T00:06:27.4076431Z # To always opt out of an experiment, prefix it with a "-". 2025-10-10T00:06:27.4077106Z # Experiments should be from the above list. 2025-10-10T00:06:27.4077505Z 2025-10-10T00:06:27.4077700Z @User1,-lf,split_build 2025-10-10T00:06:27.4078162Z @User2,lf 2025-10-10T00:06:27.4078573Z @User3,split_build 2025-10-10T00:06:27.4079014Z """ 2025-10-10T00:06:27.4079234Z 2025-10-10T00:06:27.4079406Z import json 2025-10-10T00:06:27.4079802Z import logging 2025-10-10T00:06:27.4080219Z import os 2025-10-10T00:06:27.4080609Z import random 2025-10-10T00:06:27.4081019Z import re 2025-10-10T00:06:27.4081430Z import sys 2025-10-10T00:06:27.4081864Z from argparse import ArgumentParser 2025-10-10T00:06:27.4082429Z from collections.abc import Iterable 2025-10-10T00:06:27.4082975Z from functools import cache 2025-10-10T00:06:27.4083486Z from logging import LogRecord 2025-10-10T00:06:27.4084000Z from typing import Any, NamedTuple 2025-10-10T00:06:27.4084570Z from urllib.request import Request, urlopen 2025-10-10T00:06:27.4084960Z 2025-10-10T00:06:27.4085498Z import yaml 2025-10-10T00:06:27.4085961Z from github import Auth, Github 2025-10-10T00:06:27.4086478Z from github.Issue import Issue 2025-10-10T00:06:27.4086811Z 2025-10-10T00:06:27.4086819Z 2025-10-10T00:06:27.4087043Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-10-10T00:06:27.4088238Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-10-10T00:06:27.4089372Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-10-10T00:06:27.4090101Z 2025-10-10T00:06:27.4090455Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-10-10T00:06:27.4091227Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-10-10T00:06:27.4091799Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-10-10T00:06:27.4092382Z OPT_OUT_LABEL = "no-runner-experiments" 2025-10-10T00:06:27.4092762Z 2025-10-10T00:06:27.4092964Z SETTING_EXPERIMENTS = "experiments" 2025-10-10T00:06:27.4093324Z 2025-10-10T00:06:27.4093525Z LF_FLEET_EXPERIMENT = "lf" 2025-10-10T00:06:27.4094019Z CANARY_FLEET_SUFFIX = ".c" 2025-10-10T00:06:27.4094324Z 2025-10-10T00:06:27.4094331Z 2025-10-10T00:06:27.4094528Z class Experiment(NamedTuple): 2025-10-10T00:06:27.4095234Z rollout_perc: float = ( 2025-10-10T00:06:27.4095932Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-10-10T00:06:27.4096635Z ) 2025-10-10T00:06:27.4097037Z all_branches: bool = ( 2025-10-10T00:06:27.4097690Z False # If True, the experiment is also enabled on the exception branches 2025-10-10T00:06:27.4098387Z ) 2025-10-10T00:06:27.4098784Z default: bool = ( 2025-10-10T00:06:27.4099378Z True # If True, the experiment is enabled by default for all queries 2025-10-10T00:06:27.4100049Z ) 2025-10-10T00:06:27.4100261Z 2025-10-10T00:06:27.4100451Z # Add more fields as needed 2025-10-10T00:06:27.4100776Z 2025-10-10T00:06:27.4100784Z 2025-10-10T00:06:27.4100979Z class Settings(NamedTuple): 2025-10-10T00:06:27.4101454Z """ 2025-10-10T00:06:27.4101936Z Settings for the experiments that can be opted into. 2025-10-10T00:06:27.4102541Z """ 2025-10-10T00:06:27.4102761Z 2025-10-10T00:06:27.4102980Z experiments: dict[str, Experiment] = {} 2025-10-10T00:06:27.4103368Z 2025-10-10T00:06:27.4103375Z 2025-10-10T00:06:27.4103588Z class ColorFormatter(logging.Formatter): 2025-10-10T00:06:27.4104232Z """Color codes the log messages based on the log level""" 2025-10-10T00:06:27.4104697Z 2025-10-10T00:06:27.4104866Z COLORS = { 2025-10-10T00:06:27.4105515Z "WARNING": "\033[33m", # Yellow 2025-10-10T00:06:27.4106220Z "ERROR": "\033[31m", # Red 2025-10-10T00:06:27.4106774Z "CRITICAL": "\033[31m", # Red 2025-10-10T00:06:27.4107308Z "INFO": "\033[0m", # Reset 2025-10-10T00:06:27.4107825Z "DEBUG": "\033[0m", # Reset 2025-10-10T00:06:27.4108319Z } 2025-10-10T00:06:27.4108543Z 2025-10-10T00:06:27.4108774Z def format(self, record: LogRecord) -> str: 2025-10-10T00:06:27.4109549Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-10-10T00:06:27.4110355Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-10-10T00:06:27.4110994Z return super().format(record) 2025-10-10T00:06:27.4111346Z 2025-10-10T00:06:27.4111353Z 2025-10-10T00:06:27.4111553Z handler = logging.StreamHandler() 2025-10-10T00:06:27.4112284Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-10-10T00:06:27.4112859Z 2025-10-10T00:06:27.4113109Z log = logging.getLogger(os.path.basename(__file__)) 2025-10-10T00:06:27.4113731Z log.addHandler(handler) 2025-10-10T00:06:27.4114207Z log.setLevel(logging.INFO) 2025-10-10T00:06:27.4114518Z 2025-10-10T00:06:27.4114526Z 2025-10-10T00:06:27.4114776Z def set_github_output(key: str, value: str) -> None: 2025-10-10T00:06:27.4115617Z """ 2025-10-10T00:06:27.4116154Z Defines outputs of the github action that invokes this script 2025-10-10T00:06:27.4116822Z """ 2025-10-10T00:06:27.4117220Z if not GITHUB_OUTPUT: 2025-10-10T00:06:27.4118301Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-10-10T00:06:27.4119431Z log.warning( 2025-10-10T00:06:27.4120303Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-10-10T00:06:27.4121264Z ) 2025-10-10T00:06:27.4131314Z print(f"::set-output name={key}::{value}") 2025-10-10T00:06:27.4131974Z return 2025-10-10T00:06:27.4132253Z 2025-10-10T00:06:27.4132633Z with open(GITHUB_OUTPUT, "a") as f: 2025-10-10T00:06:27.4133289Z log.info(f"Setting output: {key}='{value}'") 2025-10-10T00:06:27.4133891Z f.write(f"{key}={value}\n") 2025-10-10T00:06:27.4134250Z 2025-10-10T00:06:27.4134258Z 2025-10-10T00:06:27.4134574Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-10-10T00:06:27.4135494Z return frozenset( 2025-10-10T00:06:27.4136142Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-10-10T00:06:27.4136856Z ) 2025-10-10T00:06:27.4137078Z 2025-10-10T00:06:27.4137085Z 2025-10-10T00:06:27.4137275Z def parse_args() -> Any: 2025-10-10T00:06:27.4137869Z parser = ArgumentParser("Get dynamic rollout settings") 2025-10-10T00:06:27.4138764Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-10-10T00:06:27.4139563Z parser.add_argument( 2025-10-10T00:06:27.4140052Z "--github-issue-repo", 2025-10-10T00:06:27.4140554Z type=str, 2025-10-10T00:06:27.4140988Z required=False, 2025-10-10T00:06:27.4141455Z default="pytorch/test-infra", 2025-10-10T00:06:27.4142021Z help="GitHub repo to get the issue", 2025-10-10T00:06:27.4142556Z ) 2025-10-10T00:06:27.4142951Z parser.add_argument( 2025-10-10T00:06:27.4143422Z "--github-repo", 2025-10-10T00:06:27.4143880Z type=str, 2025-10-10T00:06:27.4144312Z required=True, 2025-10-10T00:06:27.4144810Z help="GitHub repo where CI is running", 2025-10-10T00:06:27.4145585Z ) 2025-10-10T00:06:27.4196657Z parser.add_argument( 2025-10-10T00:06:27.4197487Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-10-10T00:06:27.4198228Z ) 2025-10-10T00:06:27.4198637Z parser.add_argument( 2025-10-10T00:06:27.4199298Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-10-10T00:06:27.4199996Z ) 2025-10-10T00:06:27.4200410Z parser.add_argument( 2025-10-10T00:06:27.4201347Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-10-10T00:06:27.4202089Z ) 2025-10-10T00:06:27.4202498Z parser.add_argument( 2025-10-10T00:06:27.4203188Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-10-10T00:06:27.4203939Z ) 2025-10-10T00:06:27.4204334Z parser.add_argument( 2025-10-10T00:06:27.4204826Z "--github-ref-type", 2025-10-10T00:06:27.4205563Z type=str, 2025-10-10T00:06:27.4206001Z required=True, 2025-10-10T00:06:27.4206513Z help="Current GitHub ref type, branch or tag", 2025-10-10T00:06:27.4207105Z ) 2025-10-10T00:06:27.4207507Z parser.add_argument( 2025-10-10T00:06:27.4207999Z "--eligible-experiments", 2025-10-10T00:06:27.4208549Z type=_str_comma_separated_to_set, 2025-10-10T00:06:27.4209097Z required=False, 2025-10-10T00:06:27.4209562Z default="", 2025-10-10T00:06:27.4210438Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-10-10T00:06:27.4211398Z ) 2025-10-10T00:06:27.4211800Z parser.add_argument( 2025-10-10T00:06:27.4212287Z "--opt-out-experiments", 2025-10-10T00:06:27.4212813Z type=_str_comma_separated_to_set, 2025-10-10T00:06:27.4213364Z required=False, 2025-10-10T00:06:27.4213811Z default="", 2025-10-10T00:06:27.4214223Z help=( 2025-10-10T00:06:27.4214920Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-10-10T00:06:27.4216171Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-10-10T00:06:27.4217033Z ), 2025-10-10T00:06:27.4217411Z ) 2025-10-10T00:06:27.4217819Z parser.add_argument( 2025-10-10T00:06:27.4218287Z "--pr-number", 2025-10-10T00:06:27.4218744Z type=str, 2025-10-10T00:06:27.4219171Z required=False, 2025-10-10T00:06:27.4219632Z default="", 2025-10-10T00:06:27.4220276Z help="the optional PR number where this is run", 2025-10-10T00:06:27.4220881Z ) 2025-10-10T00:06:27.4221109Z 2025-10-10T00:06:27.4221316Z return parser.parse_args() 2025-10-10T00:06:27.4221680Z 2025-10-10T00:06:27.4221687Z 2025-10-10T00:06:27.4222104Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-10-10T00:06:27.4222897Z auth = Auth.Token(github_token) 2025-10-10T00:06:27.4223434Z return Github(auth=auth) 2025-10-10T00:06:27.4223753Z 2025-10-10T00:06:27.4223761Z 2025-10-10T00:06:27.4224217Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-10-10T00:06:27.4225215Z repo = gh.get_repo(repo) 2025-10-10T00:06:27.4225871Z return repo.get_issue(number=issue_num) 2025-10-10T00:06:27.4226263Z 2025-10-10T00:06:27.4226270Z 2025-10-10T00:06:27.4226469Z def get_potential_pr_author( 2025-10-10T00:06:27.4227154Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-10-10T00:06:27.4227861Z ) -> str: 2025-10-10T00:06:27.4228410Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-10-10T00:06:27.4229232Z # Fetch the actual username from the original PR. The PR number is 2025-10-10T00:06:27.4229996Z # embedded in the tag name: ciflow// 2025-10-10T00:06:27.4230421Z 2025-10-10T00:06:27.4230614Z gh = get_gh_client(github_token) 2025-10-10T00:06:27.4230972Z 2025-10-10T00:06:27.4231242Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-10-10T00:06:27.4231884Z split_tag = ref_name.split("/") 2025-10-10T00:06:27.4232413Z if ( 2025-10-10T00:06:27.4232826Z len(split_tag) == 3 2025-10-10T00:06:27.4233334Z and split_tag[0] == "ciflow" 2025-10-10T00:06:27.4233884Z and split_tag[2].isnumeric() 2025-10-10T00:06:27.4234404Z ): 2025-10-10T00:06:27.4234822Z pr_number = split_tag[2] 2025-10-10T00:06:27.4235633Z try: 2025-10-10T00:06:27.4236114Z repository = gh.get_repo(repo) 2025-10-10T00:06:27.4236750Z pull = repository.get_pull(number=int(pr_number)) 2025-10-10T00:06:27.4237379Z except Exception as e: 2025-10-10T00:06:27.4237922Z raise Exception( # noqa: TRY002 2025-10-10T00:06:27.4238617Z f"issue with pull request {pr_number} from repo {repository}" 2025-10-10T00:06:27.4239280Z ) from e 2025-10-10T00:06:27.4239838Z return pull.user.login # type: ignore[no-any-return] 2025-10-10T00:06:27.4240559Z # In all other cases, return the original input username 2025-10-10T00:06:27.4241168Z return username 2025-10-10T00:06:27.4241436Z 2025-10-10T00:06:27.4241443Z 2025-10-10T00:06:27.4241670Z def is_exception_branch(branch: str) -> bool: 2025-10-10T00:06:27.4242223Z """ 2025-10-10T00:06:27.4242897Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-10-10T00:06:27.4243701Z """ 2025-10-10T00:06:27.4244270Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-10-10T00:06:27.4244810Z 2025-10-10T00:06:27.4244818Z 2025-10-10T00:06:27.4245134Z def load_yaml(yaml_text: str) -> Any: 2025-10-10T00:06:27.4245665Z try: 2025-10-10T00:06:27.4246088Z data = yaml.safe_load(yaml_text) 2025-10-10T00:06:27.4246612Z return data 2025-10-10T00:06:27.4247061Z except yaml.YAMLError: 2025-10-10T00:06:27.4247568Z log.exception("Error loading YAML") 2025-10-10T00:06:27.4248116Z raise 2025-10-10T00:06:27.4248354Z 2025-10-10T00:06:27.4248361Z 2025-10-10T00:06:27.4248793Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-10-10T00:06:27.4249551Z """ 2025-10-10T00:06:27.4250197Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-10-10T00:06:27.4250815Z 2025-10-10T00:06:27.4251297Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:27.4252085Z and the text below is the list of opted in users. 2025-10-10T00:06:27.4252499Z 2025-10-10T00:06:27.4252881Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-10-10T00:06:27.4253592Z """ 2025-10-10T00:06:27.4254064Z rollout_state_parts = rollout_state.split("---") 2025-10-10T00:06:27.4254689Z if len(rollout_state_parts) >= 2: 2025-10-10T00:06:27.4255427Z return rollout_state_parts[0], rollout_state_parts[1] 2025-10-10T00:06:27.4256034Z else: 2025-10-10T00:06:27.4256433Z return "", rollout_state 2025-10-10T00:06:27.4256758Z 2025-10-10T00:06:27.4256764Z 2025-10-10T00:06:27.4256979Z class UserOptins(dict[str, list[str]]): 2025-10-10T00:06:27.4257501Z """ 2025-10-10T00:06:27.4258044Z Dictionary of users with a list of features they have opted into 2025-10-10T00:06:27.4258703Z """ 2025-10-10T00:06:27.4258922Z 2025-10-10T00:06:27.4258929Z 2025-10-10T00:06:27.4259281Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-10-10T00:06:27.4259953Z """ 2025-10-10T00:06:27.4260685Z 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:06:27.4261379Z 2025-10-10T00:06:27.4262000Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-10-10T00:06:27.4262995Z - Example line: "@User1,lf,split_build" 2025-10-10T00:06:27.4263687Z - A "#" prefix indicates the user is opted out of all experiments 2025-10-10T00:06:27.4264183Z 2025-10-10T00:06:27.4264190Z 2025-10-10T00:06:27.4264348Z """ 2025-10-10T00:06:27.4264752Z optins = UserOptins() 2025-10-10T00:06:27.4265538Z for user in user_optin_text.split("\n"): 2025-10-10T00:06:27.4266136Z user = user.strip("\r\n\t -") 2025-10-10T00:06:27.4266713Z if not user or not user.startswith("@"): 2025-10-10T00:06:27.4267463Z # Not a valid user. Skip 2025-10-10T00:06:27.4267984Z continue 2025-10-10T00:06:27.4268251Z 2025-10-10T00:06:27.4268412Z if user: 2025-10-10T00:06:27.4268878Z usr_name = user.split(",")[0].strip("@") 2025-10-10T00:06:27.4269588Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-10-10T00:06:27.4270095Z 2025-10-10T00:06:27.4270266Z return optins 2025-10-10T00:06:27.4270514Z 2025-10-10T00:06:27.4270521Z 2025-10-10T00:06:27.4270813Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-10-10T00:06:27.4271425Z """ 2025-10-10T00:06:27.4271845Z Check if the experiment name is valid. 2025-10-10T00:06:27.4272377Z A valid name: 2025-10-10T00:06:27.4273022Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-10-10T00:06:27.4273994Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-10-10T00:06:27.4274752Z - Cannot contain spaces 2025-10-10T00:06:27.4275354Z """ 2025-10-10T00:06:27.4275575Z 2025-10-10T00:06:27.4275840Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-10-10T00:06:27.4276560Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-10-10T00:06:27.4277012Z 2025-10-10T00:06:27.4277183Z if valid: 2025-10-10T00:06:27.4277589Z return True 2025-10-10T00:06:27.4277847Z 2025-10-10T00:06:27.4278016Z log.error( 2025-10-10T00:06:27.4279479Z 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:06:27.4281042Z ) 2025-10-10T00:06:27.4281409Z return False 2025-10-10T00:06:27.4281660Z 2025-10-10T00:06:27.4281673Z 2025-10-10T00:06:27.4281977Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-10-10T00:06:27.4282609Z """ 2025-10-10T00:06:27.4283335Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-10-10T00:06:27.4284093Z """ 2025-10-10T00:06:27.4284470Z try: 2025-10-10T00:06:27.4284867Z if settings_text: 2025-10-10T00:06:27.4285712Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-10-10T00:06:27.4286523Z # for easy reading 2025-10-10T00:06:27.4287325Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-10-10T00:06:27.4288234Z # the backtick character in shell commands. 2025-10-10T00:06:27.4288849Z backtick = chr(96) # backtick character 2025-10-10T00:06:27.4289540Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-10-10T00:06:27.4290230Z settings = load_yaml(settings_text) 2025-10-10T00:06:27.4290617Z 2025-10-10T00:06:27.4291032Z # For now we just load experiments. We can expand this if/when we add more settings 2025-10-10T00:06:27.4291816Z experiments = {} 2025-10-10T00:06:27.4292126Z 2025-10-10T00:06:27.4292508Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-10-10T00:06:27.4293302Z if not is_valid_experiment_name(exp_name): 2025-10-10T00:06:27.4294425Z # 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:06:27.4295626Z continue 2025-10-10T00:06:27.4295933Z 2025-10-10T00:06:27.4296133Z valid_settings = {} 2025-10-10T00:06:27.4296669Z for setting in exp_settings: 2025-10-10T00:06:27.4297266Z if setting not in Experiment._fields: 2025-10-10T00:06:27.4297833Z log.warning( 2025-10-10T00:06:27.4298565Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-10-10T00:06:27.4299452Z ) 2025-10-10T00:06:27.4299907Z else: 2025-10-10T00:06:27.4300433Z valid_settings[setting] = exp_settings[setting] 2025-10-10T00:06:27.4300870Z 2025-10-10T00:06:27.4301146Z experiments[exp_name] = Experiment(**valid_settings) 2025-10-10T00:06:27.4301793Z return Settings(experiments) 2025-10-10T00:06:27.4302156Z 2025-10-10T00:06:27.4302336Z except Exception: 2025-10-10T00:06:27.4302835Z log.exception("Failed to parse settings") 2025-10-10T00:06:27.4303235Z 2025-10-10T00:06:27.4303410Z return Settings() 2025-10-10T00:06:27.4303685Z 2025-10-10T00:06:27.4303691Z 2025-10-10T00:06:27.4303935Z def parse_settings(rollout_state: str) -> Settings: 2025-10-10T00:06:27.4304533Z """ 2025-10-10T00:06:27.4304986Z Parse settings, if any, from the rollout state. 2025-10-10T00:06:27.4305717Z 2025-10-10T00:06:27.4306080Z If the issue body contains "---" then the text above that is the settings 2025-10-10T00:06:27.4306880Z and the text below is the list of opted in users. 2025-10-10T00:06:27.4307317Z 2025-10-10T00:06:27.4307721Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-10-10T00:06:27.4308470Z """ 2025-10-10T00:06:27.4309052Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:27.4309839Z return parse_settings_from_text(settings_text) 2025-10-10T00:06:27.4310255Z 2025-10-10T00:06:27.4310262Z 2025-10-10T00:06:27.4310508Z def parse_users(rollout_state: str) -> UserOptins: 2025-10-10T00:06:27.4311092Z """ 2025-10-10T00:06:27.4311499Z Parse users from the rollout state. 2025-10-10T00:06:27.4311875Z 2025-10-10T00:06:27.4312038Z """ 2025-10-10T00:06:27.4312586Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-10-10T00:06:27.4313339Z return parse_user_opt_in_from_text(users_text) 2025-10-10T00:06:27.4313783Z 2025-10-10T00:06:27.4313790Z 2025-10-10T00:06:27.4314355Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:27.4315611Z """ 2025-10-10T00:06:27.4316085Z Check if a user is opted into an experiment 2025-10-10T00:06:27.4316644Z """ 2025-10-10T00:06:27.4317124Z return experiment_name in user_optins.get(user, []) 2025-10-10T00:06:27.4317558Z 2025-10-10T00:06:27.4317566Z 2025-10-10T00:06:27.4317990Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-10-10T00:06:27.4318757Z """ 2025-10-10T00:06:27.4319240Z Check if a user explicitly opted out of an experiment 2025-10-10T00:06:27.4319835Z """ 2025-10-10T00:06:27.4320372Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-10-10T00:06:27.4321076Z experiment_optout = "-" + experiment_name 2025-10-10T00:06:27.4321777Z if experiment_optout not in user_optins.get(user, []): 2025-10-10T00:06:27.4322413Z return False 2025-10-10T00:06:27.4322693Z 2025-10-10T00:06:27.4322977Z if is_user_opted_in(user, user_optins, experiment_name): 2025-10-10T00:06:27.4323606Z log.warning( 2025-10-10T00:06:27.4324426Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-10-10T00:06:27.4325450Z ) 2025-10-10T00:06:27.4325675Z 2025-10-10T00:06:27.4325848Z return True 2025-10-10T00:06:27.4326102Z 2025-10-10T00:06:27.4326109Z 2025-10-10T00:06:27.4326293Z def get_runner_prefix( 2025-10-10T00:06:27.4326755Z rollout_state: str, 2025-10-10T00:06:27.4327231Z workflow_requestors: Iterable[str], 2025-10-10T00:06:27.4327773Z branch: str, 2025-10-10T00:06:27.4328276Z eligible_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:27.4328956Z opt_out_experiments: frozenset[str] = frozenset(), 2025-10-10T00:06:27.4329557Z is_canary: bool = False, 2025-10-10T00:06:27.4330034Z ) -> str: 2025-10-10T00:06:27.4330648Z settings = parse_settings(rollout_state) 2025-10-10T00:06:27.4331261Z user_optins = parse_users(rollout_state) 2025-10-10T00:06:27.4331645Z 2025-10-10T00:06:27.4331831Z fleet_prefix = "" 2025-10-10T00:06:27.4332271Z prefixes = [] 2025-10-10T00:06:27.4332911Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-10-10T00:06:27.4333859Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-10-10T00:06:27.4334587Z log.info( 2025-10-10T00:06:27.4335390Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-10-10T00:06:27.4336170Z ) 2025-10-10T00:06:27.4336563Z continue 2025-10-10T00:06:27.4336838Z 2025-10-10T00:06:27.4337027Z if opt_out_experiments: 2025-10-10T00:06:27.4337581Z if experiment_name in opt_out_experiments: 2025-10-10T00:06:27.4338234Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-10-10T00:06:27.4338845Z log.info( 2025-10-10T00:06:27.4339782Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-10-10T00:06:27.4340782Z ) 2025-10-10T00:06:27.4341189Z continue 2025-10-10T00:06:27.4341481Z 2025-10-10T00:06:27.4341672Z if eligible_experiments: 2025-10-10T00:06:27.4342270Z if experiment_name not in eligible_experiments: 2025-10-10T00:06:27.4342914Z exp_list = ", ".join(eligible_experiments) 2025-10-10T00:06:27.4343491Z log.info( 2025-10-10T00:06:27.4344283Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-10-10T00:06:27.4345232Z ) 2025-10-10T00:06:27.4345652Z continue 2025-10-10T00:06:27.4346147Z elif not experiment_settings.default: 2025-10-10T00:06:27.4346688Z log.info( 2025-10-10T00:06:27.4347493Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-10-10T00:06:27.4348265Z ) 2025-10-10T00:06:27.4348654Z continue 2025-10-10T00:06:27.4348917Z 2025-10-10T00:06:27.4349197Z # Is any workflow_requestor opted out to this experiment? 2025-10-10T00:06:27.4349830Z opted_out_users = [ 2025-10-10T00:06:27.4350296Z requestor 2025-10-10T00:06:27.4350767Z for requestor in workflow_requestors 2025-10-10T00:06:27.4351454Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-10-10T00:06:27.4352098Z ] 2025-10-10T00:06:27.4352319Z 2025-10-10T00:06:27.4352499Z if opted_out_users: 2025-10-10T00:06:27.4352957Z log.info( 2025-10-10T00:06:27.4353589Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-10-10T00:06:27.4354303Z ) 2025-10-10T00:06:27.4354690Z continue 2025-10-10T00:06:27.4354965Z 2025-10-10T00:06:27.4355361Z # Is any workflow_requestor opted in to this experiment? 2025-10-10T00:06:27.4356011Z opted_in_users = [ 2025-10-10T00:06:27.4356469Z requestor 2025-10-10T00:06:27.4356939Z for requestor in workflow_requestors 2025-10-10T00:06:27.4357623Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-10-10T00:06:27.4358266Z ] 2025-10-10T00:06:27.4358484Z 2025-10-10T00:06:27.4358656Z enabled = False 2025-10-10T00:06:27.4359100Z if opted_in_users: 2025-10-10T00:06:27.4359550Z log.info( 2025-10-10T00:06:27.4360167Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-10-10T00:06:27.4360867Z ) 2025-10-10T00:06:27.4361269Z enabled = True 2025-10-10T00:06:27.4361566Z 2025-10-10T00:06:27.4361788Z elif experiment_settings.rollout_perc: 2025-10-10T00:06:27.4362633Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-10-10T00:06:27.4363724Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-10-10T00:06:27.4364393Z log.info( 2025-10-10T00:06:27.4365373Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-10-10T00:06:27.4366303Z ) 2025-10-10T00:06:27.4366728Z enabled = True 2025-10-10T00:06:27.4367041Z 2025-10-10T00:06:27.4367210Z if enabled: 2025-10-10T00:06:27.4367655Z label = experiment_name 2025-10-10T00:06:27.4368228Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-10-10T00:06:27.4369078Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-10-10T00:06:27.4369982Z # - If it's enabled, then we always list it's prefix first 2025-10-10T00:06:27.4370759Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-10-10T00:06:27.4371456Z if is_canary: 2025-10-10T00:06:27.4371967Z label += CANARY_FLEET_SUFFIX 2025-10-10T00:06:27.4372527Z fleet_prefix = label 2025-10-10T00:06:27.4373038Z else: 2025-10-10T00:06:27.4373475Z prefixes.append(label) 2025-10-10T00:06:27.4373839Z 2025-10-10T00:06:27.4374025Z if len(prefixes) > 1: 2025-10-10T00:06:27.4374482Z log.error( 2025-10-10T00:06:27.4375627Z 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:06:27.4376758Z ) 2025-10-10T00:06:27.4377165Z prefixes = prefixes[:1] 2025-10-10T00:06:27.4377488Z 2025-10-10T00:06:27.4377682Z # Fleet always comes first 2025-10-10T00:06:27.4378168Z if fleet_prefix: 2025-10-10T00:06:27.4378637Z prefixes.insert(0, fleet_prefix) 2025-10-10T00:06:27.4379014Z 2025-10-10T00:06:27.4379391Z return ".".join(prefixes) + "." if prefixes else "" 2025-10-10T00:06:27.4379836Z 2025-10-10T00:06:27.4379843Z 2025-10-10T00:06:27.4380290Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-10-10T00:06:27.4381085Z """ 2025-10-10T00:06:27.4381676Z Gets the first comment of the issue, which contains the desired rollout state. 2025-10-10T00:06:27.4382249Z 2025-10-10T00:06:27.4382638Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-10-10T00:06:27.4383353Z """ 2025-10-10T00:06:27.4383760Z gh = get_gh_client(github_token) 2025-10-10T00:06:27.4384311Z issue = get_issue(gh, repo, issue_num) 2025-10-10T00:06:27.4384966Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-10-10T00:06:27.4385531Z 2025-10-10T00:06:27.4385538Z 2025-10-10T00:06:27.4385941Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-10-10T00:06:27.4386726Z for _ in range(num_retries): 2025-10-10T00:06:27.4387222Z try: 2025-10-10T00:06:27.4387663Z req = Request(url=url, headers=headers) 2025-10-10T00:06:27.4388344Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-10-10T00:06:27.4388998Z return json.loads(content) 2025-10-10T00:06:27.4389545Z except Exception as e: 2025-10-10T00:06:27.4390094Z log.warning(f"Could not download {url}: {e}") 2025-10-10T00:06:27.4390519Z 2025-10-10T00:06:27.4390900Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-10-10T00:06:27.4391639Z return {} 2025-10-10T00:06:27.4391880Z 2025-10-10T00:06:27.4391888Z 2025-10-10T00:06:27.4392044Z @cache 2025-10-10T00:06:27.4392676Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-10-10T00:06:27.4393442Z """ 2025-10-10T00:06:27.4393859Z Dynamically get PR information 2025-10-10T00:06:27.4394510Z """ 2025-10-10T00:06:27.4395130Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-10-10T00:06:27.4395778Z headers = { 2025-10-10T00:06:27.4396251Z "Accept": "application/vnd.github.v3+json", 2025-10-10T00:06:27.4396873Z "Authorization": f"token {github_token}", 2025-10-10T00:06:27.4397431Z } 2025-10-10T00:06:27.4397875Z json_response: dict[str, Any] = download_json( 2025-10-10T00:06:27.4398488Z url=f"{github_api}/issues/{pr_number}", 2025-10-10T00:06:27.4399047Z headers=headers, 2025-10-10T00:06:27.4399488Z ) 2025-10-10T00:06:27.4399701Z 2025-10-10T00:06:27.4399887Z if not json_response: 2025-10-10T00:06:27.4400470Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-10-10T00:06:27.4401096Z return {} 2025-10-10T00:06:27.4401343Z 2025-10-10T00:06:27.4401529Z return json_response 2025-10-10T00:06:27.4401817Z 2025-10-10T00:06:27.4401824Z 2025-10-10T00:06:27.4402225Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-10-10T00:06:27.4402983Z """ 2025-10-10T00:06:27.4403516Z Dynamically get the latest list of labels from the pull request 2025-10-10T00:06:27.4404195Z """ 2025-10-10T00:06:27.4404684Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-10-10T00:06:27.4405420Z return { 2025-10-10T00:06:27.4406037Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-10-10T00:06:27.4406745Z } 2025-10-10T00:06:27.4406958Z 2025-10-10T00:06:27.4406966Z 2025-10-10T00:06:27.4407144Z def main() -> None: 2025-10-10T00:06:27.4407579Z args = parse_args() 2025-10-10T00:06:27.4407865Z 2025-10-10T00:06:27.4408086Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-10-10T00:06:27.4408491Z 2025-10-10T00:06:27.4408683Z # Check if the PR is opt-out 2025-10-10T00:06:27.4409190Z if args.pr_number: 2025-10-10T00:06:27.4409853Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-10-10T00:06:27.4410759Z if OPT_OUT_LABEL in labels: 2025-10-10T00:06:27.4411300Z log.info( 2025-10-10T00:06:27.4412005Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-10-10T00:06:27.4412793Z ) 2025-10-10T00:06:27.4413354Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:27.4414042Z sys.exit() 2025-10-10T00:06:27.4414335Z 2025-10-10T00:06:27.4414505Z try: 2025-10-10T00:06:27.4414959Z rollout_state = get_rollout_state_from_issue( 2025-10-10T00:06:27.4415789Z args.github_token, args.github_issue_repo, args.github_issue 2025-10-10T00:06:27.4416437Z ) 2025-10-10T00:06:27.4416656Z 2025-10-10T00:06:27.4416870Z username = get_potential_pr_author( 2025-10-10T00:06:27.4417432Z args.github_token, 2025-10-10T00:06:27.4417931Z args.github_repo, 2025-10-10T00:06:27.4418418Z args.github_actor, 2025-10-10T00:06:27.4418925Z args.github_ref_type, 2025-10-10T00:06:27.4419443Z args.github_branch, 2025-10-10T00:06:27.4419932Z ) 2025-10-10T00:06:27.4420154Z 2025-10-10T00:06:27.4420443Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-10-10T00:06:27.4420907Z 2025-10-10T00:06:27.4421127Z runner_label_prefix = get_runner_prefix( 2025-10-10T00:06:27.4421734Z rollout_state, 2025-10-10T00:06:27.4422228Z (args.github_issue_owner, username), 2025-10-10T00:06:27.4422798Z args.github_branch, 2025-10-10T00:06:27.4423312Z args.eligible_experiments, 2025-10-10T00:06:27.4423864Z args.opt_out_experiments, 2025-10-10T00:06:27.4424376Z is_canary, 2025-10-10T00:06:27.4424807Z ) 2025-10-10T00:06:27.4425120Z 2025-10-10T00:06:27.4425320Z except Exception as e: 2025-10-10T00:06:27.4425790Z log.error( 2025-10-10T00:06:27.4426478Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-10-10T00:06:27.4427406Z ) 2025-10-10T00:06:27.4427639Z 2025-10-10T00:06:27.4427969Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-10-10T00:06:27.4428482Z 2025-10-10T00:06:27.4428489Z 2025-10-10T00:06:27.4428671Z if __name__ == "__main__": 2025-10-10T00:06:27.4429128Z main() 2025-10-10T00:06:27.4429353Z 2025-10-10T00:06:27.4523313Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:06:27.4524195Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-10-10T00:06:27.4557084Z shell: /usr/bin/bash -e {0} 2025-10-10T00:06:27.4557564Z env: 2025-10-10T00:06:27.4558189Z GITHUB_TOKEN: *** 2025-10-10T00:06:27.4558600Z ISSUE_NUMBER: 5132 2025-10-10T00:06:27.4559049Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:06:27.4559547Z ISSUE_OWNER: 2025-10-10T00:06:27.4559938Z CHECK_EXPERIMENTS: 2025-10-10T00:06:27.4560356Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:06:27.4560790Z PR_NUMBER: 2025-10-10T00:06:27.4561166Z ##[endgroup] 2025-10-10T00:06:29.7581006Z Defaulting to user installation because normal site-packages is not writeable 2025-10-10T00:06:30.7725379Z Collecting urllib3==1.26.18 2025-10-10T00:06:30.8349298Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-10-10T00:06:30.8597571Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.5 MB/s eta 0:00:00 2025-10-10T00:06:30.8901419Z Collecting PyGithub==2.3.0 2025-10-10T00:06:30.9082494Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-10-10T00:06:30.9588149Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-10-10T00:06:30.9686591Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.4 kB) 2025-10-10T00:06:30.9731070Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-10-10T00:06:30.9748311Z 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:06:30.9763590Z 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:06:31.0067905Z Collecting Deprecated (from PyGithub==2.3.0) 2025-10-10T00:06:31.0292655Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-10-10T00:06:31.0523739Z 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:06:31.1937370Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:06:31.2038722Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-10-10T00:06:31.3447819Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-10-10T00:06:31.3552382Z 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:06:31.3839356Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-10-10T00:06:31.3940876Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-10-10T00:06:31.4237657Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-10-10T00:06:31.4405651Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 9.1 MB/s eta 0:00:00 2025-10-10T00:06:31.4517171Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-10-10T00:06:31.4842806Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 11.2 MB/s eta 0:00:00 2025-10-10T00:06:31.4941654Z Downloading pynacl-1.6.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-10-10T00:06:31.5848892Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 15.6 MB/s eta 0:00:00 2025-10-10T00:06:31.5974672Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-10-10T00:06:31.6093735Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-10-10T00:06:31.6217430Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 19.8 MB/s eta 0:00:00 2025-10-10T00:06:31.6316245Z 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:06:31.6365902Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 24.0 MB/s eta 0:00:00 2025-10-10T00:06:31.6460166Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-10-10T00:06:31.6525439Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 21.9 MB/s eta 0:00:00 2025-10-10T00:06:32.0104896Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-10-10T00:06:32.5443076Z 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:06:32.6253546Z ##[group]Run curr_branch="main" 2025-10-10T00:06:32.6253854Z curr_branch="main" 2025-10-10T00:06:32.6254079Z curr_ref_type="branch" 2025-10-10T00:06:32.6254328Z echo "Current branch is '$curr_branch'" 2025-10-10T00:06:32.6254609Z  2025-10-10T00:06:32.6254793Z python3 runner_determinator.py \ 2025-10-10T00:06:32.6255377Z  --github-token "$GITHUB_TOKEN" \ 2025-10-10T00:06:32.6255747Z  --github-issue "$ISSUE_NUMBER" \ 2025-10-10T00:06:32.6255995Z  --github-branch "$curr_branch" \ 2025-10-10T00:06:32.6256260Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-10-10T00:06:32.6256535Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-10-10T00:06:32.6256810Z  --github-ref-type "$curr_ref_type" \ 2025-10-10T00:06:32.6257080Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-10-10T00:06:32.6257383Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-10-10T00:06:32.6257749Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-10-10T00:06:32.6258065Z  --pr-number "${PR_NUMBER}" 2025-10-10T00:06:32.6291442Z shell: /usr/bin/bash -e {0} 2025-10-10T00:06:32.6291666Z env: 2025-10-10T00:06:32.6292244Z GITHUB_TOKEN: *** 2025-10-10T00:06:32.6292442Z ISSUE_NUMBER: 5132 2025-10-10T00:06:32.6292644Z TRIGGERING_ACTOR: pytorchmergebot 2025-10-10T00:06:32.6292886Z ISSUE_OWNER: 2025-10-10T00:06:32.6293063Z CHECK_EXPERIMENTS: 2025-10-10T00:06:32.6293264Z OPT_OUT_EXPERIMENTS: 2025-10-10T00:06:32.6293455Z PR_NUMBER: 2025-10-10T00:06:32.6293623Z ##[endgroup] 2025-10-10T00:06:32.6344208Z Current branch is 'main' 2025-10-10T00:06:34.4184714Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-10-10T00:06:34.4185979Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-10-10T00:06:34.4186692Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-10-10T00:06:34.4187229Z INFO : Setting output: label-type='' 2025-10-10T00:06:34.4531796Z Evaluate and set job outputs 2025-10-10T00:06:34.4538901Z Cleaning up orphan processes