2025-09-07T06:09:28.1222995Z Current runner version: '2.328.0' 2025-09-07T06:09:28.1248254Z ##[group]Runner Image Provisioner 2025-09-07T06:09:28.1249015Z Hosted Compute Agent 2025-09-07T06:09:28.1249618Z Version: 20250829.383 2025-09-07T06:09:28.1250207Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T06:09:28.1250885Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T06:09:28.1251418Z ##[endgroup] 2025-09-07T06:09:28.1252053Z ##[group]Operating System 2025-09-07T06:09:28.1252569Z Ubuntu 2025-09-07T06:09:28.1253036Z 24.04.3 2025-09-07T06:09:28.1253566Z LTS 2025-09-07T06:09:28.1253975Z ##[endgroup] 2025-09-07T06:09:28.1254655Z ##[group]Runner Image 2025-09-07T06:09:28.1255591Z Image: ubuntu-24.04 2025-09-07T06:09:28.1256157Z Version: 20250831.1.0 2025-09-07T06:09:28.1257121Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T06:09:28.1258734Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T06:09:28.1259654Z ##[endgroup] 2025-09-07T06:09:28.1260867Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T06:09:28.1263027Z Contents: read 2025-09-07T06:09:28.1263596Z Metadata: read 2025-09-07T06:09:28.1264112Z Packages: read 2025-09-07T06:09:28.1264896Z ##[endgroup] 2025-09-07T06:09:28.1267799Z Secret source: Actions 2025-09-07T06:09:28.1268685Z Prepare workflow directory 2025-09-07T06:09:28.1799102Z Prepare all required actions 2025-09-07T06:09:28.1857278Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T06:09:28.1862459Z ##[group] Inputs 2025-09-07T06:09:28.1863042Z check_experiments: 2025-09-07T06:09:28.1863724Z opt_out_experiments: 2025-09-07T06:09:28.1864294Z triggering_actor: pytorchmergebot 2025-09-07T06:09:28.1865284Z issue_owner: 2025-09-07T06:09:28.1865862Z curr_branch: main 2025-09-07T06:09:28.1866464Z curr_ref_type: branch 2025-09-07T06:09:28.1866992Z issue_number: 5132 2025-09-07T06:09:28.1867580Z ##[endgroup] 2025-09-07T06:09:28.1868192Z Complete job name: get-label-type / runner-determinator 2025-09-07T06:09:28.2468387Z ##[group]Run cat < runner_determinator.py 2025-09-07T06:09:28.2471470Z cat < runner_determinator.py 2025-09-07T06:09:28.2472127Z # flake8: noqa: G004 2025-09-07T06:09:28.2472711Z  2025-09-07T06:09:28.2473505Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:28.2474919Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:28.2475981Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:28.2476659Z  2025-09-07T06:09:28.2477113Z """ 2025-09-07T06:09:28.2477896Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:28.2478861Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:28.2480056Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:28.2480992Z of which runners should be used to run which job. 2025-09-07T06:09:28.2481647Z  2025-09-07T06:09:28.2482281Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:28.2483396Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:28.2484377Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:28.2485356Z list, defined. 2025-09-07T06:09:28.2485886Z  2025-09-07T06:09:28.2486519Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:28.2487583Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:28.2488551Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:28.2489514Z  2025-09-07T06:09:28.2490321Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:28.2491288Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:28.2492149Z experiments which the user could be opted in to. 2025-09-07T06:09:28.2492806Z  2025-09-07T06:09:28.2493351Z The user list has the following rules: 2025-09-07T06:09:28.2493953Z  2025-09-07T06:09:28.2494703Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:28.2495686Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:28.2496529Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:28.2497240Z  2025-09-07T06:09:28.2497671Z Example config: 2025-09-07T06:09:28.2498266Z  # A list of experiments that can be opted into. 2025-09-07T06:09:28.2499107Z  # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:28.2499829Z  # Expected syntax is: 2025-09-07T06:09:28.2500612Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:28.2501615Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:28.2502563Z  2025-09-07T06:09:28.2503017Z  experiments: 2025-09-07T06:09:28.2503529Z  lf: 2025-09-07T06:09:28.2504041Z  rollout_percent: 25 2025-09-07T06:09:28.2504974Z  all_branches: false 2025-09-07T06:09:28.2505770Z  default: true 2025-09-07T06:09:28.2506298Z  --- 2025-09-07T06:09:28.2506767Z  2025-09-07T06:09:28.2507246Z  # Opt-ins: 2025-09-07T06:09:28.2507960Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:28.2509141Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:28.2510161Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:28.2510951Z  # Experiments should be from the above list. 2025-09-07T06:09:28.2511546Z  2025-09-07T06:09:28.2512050Z  @User1,-lf,split_build 2025-09-07T06:09:28.2512634Z  @User2,lf 2025-09-07T06:09:28.2513120Z  @User3,split_build 2025-09-07T06:09:28.2513735Z """ 2025-09-07T06:09:28.2514176Z  2025-09-07T06:09:28.2514897Z import json 2025-09-07T06:09:28.2515371Z import logging 2025-09-07T06:09:28.2516056Z import os 2025-09-07T06:09:28.2516563Z import random 2025-09-07T06:09:28.2517063Z import re 2025-09-07T06:09:28.2517687Z import sys 2025-09-07T06:09:28.2518197Z from argparse import ArgumentParser 2025-09-07T06:09:28.2518920Z from collections.abc import Iterable 2025-09-07T06:09:28.2519593Z from functools import cache 2025-09-07T06:09:28.2520167Z from logging import LogRecord 2025-09-07T06:09:28.2520869Z from typing import Any, NamedTuple 2025-09-07T06:09:28.2521535Z from urllib.request import Request, urlopen 2025-09-07T06:09:28.2522175Z  2025-09-07T06:09:28.2522727Z import yaml 2025-09-07T06:09:28.2523327Z from github import Auth, Github 2025-09-07T06:09:28.2523947Z from github.Issue import Issue 2025-09-07T06:09:28.2524637Z  2025-09-07T06:09:28.2525197Z  2025-09-07T06:09:28.2525690Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:28.2526497Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:28.2527493Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:28.2528524Z  2025-09-07T06:09:28.2529157Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:28.2529885Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:28.2530540Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:28.2531177Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:28.2531872Z  2025-09-07T06:09:28.2532330Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:28.2532951Z  2025-09-07T06:09:28.2533481Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:28.2534039Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:28.2534708Z  2025-09-07T06:09:28.2535190Z  2025-09-07T06:09:28.2535726Z class Experiment(NamedTuple): 2025-09-07T06:09:28.2536286Z  rollout_perc: float = ( 2025-09-07T06:09:28.2537138Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:28.2537915Z  ) 2025-09-07T06:09:28.2538360Z  all_branches: bool = ( 2025-09-07T06:09:28.2539200Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:28.2540012Z  ) 2025-09-07T06:09:28.2540480Z  default: bool = ( 2025-09-07T06:09:28.2541224Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:28.2541973Z  ) 2025-09-07T06:09:28.2542496Z  2025-09-07T06:09:28.2542952Z  # Add more fields as needed 2025-09-07T06:09:28.2543566Z  2025-09-07T06:09:28.2543944Z  2025-09-07T06:09:28.2544736Z class Settings(NamedTuple): 2025-09-07T06:09:28.2545389Z  """ 2025-09-07T06:09:28.2545969Z  Settings for the experiments that can be opted into. 2025-09-07T06:09:28.2546720Z  """ 2025-09-07T06:09:28.2547266Z  2025-09-07T06:09:28.2547756Z  experiments: dict[str, Experiment] = {} 2025-09-07T06:09:28.2548444Z  2025-09-07T06:09:28.2549047Z  2025-09-07T06:09:28.2549545Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:28.2550396Z  """Color codes the log messages based on the log level""" 2025-09-07T06:09:28.2551079Z  2025-09-07T06:09:28.2551526Z  COLORS = { 2025-09-07T06:09:28.2552122Z  "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:28.2552719Z  "ERROR": "\033[31m", # Red 2025-09-07T06:09:28.2553338Z  "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:28.2554012Z  "INFO": "\033[0m", # Reset 2025-09-07T06:09:28.2555065Z  "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:28.2555628Z  } 2025-09-07T06:09:28.2556155Z  2025-09-07T06:09:28.2556686Z  def format(self, record: LogRecord) -> str: 2025-09-07T06:09:28.2557522Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:28.2558464Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:28.2559158Z  return super().format(record) 2025-09-07T06:09:28.2559765Z  2025-09-07T06:09:28.2560196Z  2025-09-07T06:09:28.2560701Z handler = logging.StreamHandler() 2025-09-07T06:09:28.2561537Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:28.2562420Z  2025-09-07T06:09:28.2563043Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:28.2563706Z log.addHandler(handler) 2025-09-07T06:09:28.2564337Z log.setLevel(logging.INFO) 2025-09-07T06:09:28.2565034Z  2025-09-07T06:09:28.2565469Z  2025-09-07T06:09:28.2566090Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:28.2566764Z  """ 2025-09-07T06:09:28.2567399Z  Defines outputs of the github action that invokes this script 2025-09-07T06:09:28.2652641Z  """ 2025-09-07T06:09:28.2653437Z  if not GITHUB_OUTPUT: 2025-09-07T06:09:28.2655580Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:28.2657606Z  log.warning( 2025-09-07T06:09:28.2658719Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T06:09:28.2659651Z  ) 2025-09-07T06:09:28.2660117Z  print(f"::set-output name={key}::{value}") 2025-09-07T06:09:28.2660671Z  return 2025-09-07T06:09:28.2661067Z  2025-09-07T06:09:28.2661467Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:28.2662060Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:28.2662639Z  f.write(f"{key}={value}\n") 2025-09-07T06:09:28.2663147Z  2025-09-07T06:09:28.2663485Z  2025-09-07T06:09:28.2664016Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:28.2665041Z  return frozenset( 2025-09-07T06:09:28.2665706Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:28.2666384Z  ) 2025-09-07T06:09:28.2666747Z  2025-09-07T06:09:28.2667081Z  2025-09-07T06:09:28.2667446Z def parse_args() -> Any: 2025-09-07T06:09:28.2668049Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:28.2668919Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:28.2669672Z  parser.add_argument( 2025-09-07T06:09:28.2670155Z  "--github-issue-repo", 2025-09-07T06:09:28.2670649Z  type=str, 2025-09-07T06:09:28.2671089Z  required=False, 2025-09-07T06:09:28.2671834Z  default="pytorch/test-infra", 2025-09-07T06:09:28.2672417Z  help="GitHub repo to get the issue", 2025-09-07T06:09:28.2672934Z  ) 2025-09-07T06:09:28.2673328Z  parser.add_argument( 2025-09-07T06:09:28.2673802Z  "--github-repo", 2025-09-07T06:09:28.2674262Z  type=str, 2025-09-07T06:09:28.2675391Z  required=True, 2025-09-07T06:09:28.2675912Z  help="GitHub repo where CI is running", 2025-09-07T06:09:28.2676427Z  ) 2025-09-07T06:09:28.2676814Z  parser.add_argument( 2025-09-07T06:09:28.2677468Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:28.2678123Z  ) 2025-09-07T06:09:28.2678519Z  parser.add_argument( 2025-09-07T06:09:28.2679179Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:28.2679869Z  ) 2025-09-07T06:09:28.2680259Z  parser.add_argument( 2025-09-07T06:09:28.2680935Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:28.2681610Z  ) 2025-09-07T06:09:28.2681998Z  parser.add_argument( 2025-09-07T06:09:28.2682699Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:28.2683419Z  ) 2025-09-07T06:09:28.2683804Z  parser.add_argument( 2025-09-07T06:09:28.2684285Z  "--github-ref-type", 2025-09-07T06:09:28.2684956Z  type=str, 2025-09-07T06:09:28.2685397Z  required=True, 2025-09-07T06:09:28.2685925Z  help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:28.2686472Z  ) 2025-09-07T06:09:28.2686860Z  parser.add_argument( 2025-09-07T06:09:28.2687520Z  "--eligible-experiments", 2025-09-07T06:09:28.2688070Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:28.2688587Z  required=False, 2025-09-07T06:09:28.2689057Z  default="", 2025-09-07T06:09:28.2689932Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:28.2690831Z  ) 2025-09-07T06:09:28.2691217Z  parser.add_argument( 2025-09-07T06:09:28.2691710Z  "--opt-out-experiments", 2025-09-07T06:09:28.2692247Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:28.2692769Z  required=False, 2025-09-07T06:09:28.2693232Z  default="", 2025-09-07T06:09:28.2693670Z  help=( 2025-09-07T06:09:28.2694364Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:28.2695798Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:28.2696630Z  ), 2025-09-07T06:09:28.2697006Z  ) 2025-09-07T06:09:28.2697392Z  parser.add_argument( 2025-09-07T06:09:28.2697864Z  "--pr-number", 2025-09-07T06:09:28.2698317Z  type=str, 2025-09-07T06:09:28.2698749Z  required=False, 2025-09-07T06:09:28.2699208Z  default="", 2025-09-07T06:09:28.2699722Z  help="the optional PR number where this is run", 2025-09-07T06:09:28.2700280Z  ) 2025-09-07T06:09:28.2700631Z  2025-09-07T06:09:28.2701013Z  return parser.parse_args() 2025-09-07T06:09:28.2701483Z  2025-09-07T06:09:28.2701821Z  2025-09-07T06:09:28.2702428Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.2703420Z  auth = Auth.Token(github_token) 2025-09-07T06:09:28.2703957Z  return Github(auth=auth) 2025-09-07T06:09:28.2704418Z  2025-09-07T06:09:28.2705001Z  2025-09-07T06:09:28.2705641Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.2706428Z  repo = gh.get_repo(repo) 2025-09-07T06:09:28.2706963Z  return repo.get_issue(number=issue_num) 2025-09-07T06:09:28.2707467Z  2025-09-07T06:09:28.2707802Z  2025-09-07T06:09:28.2708168Z def get_potential_pr_author( 2025-09-07T06:09:28.2708840Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:28.2709505Z ) -> str: 2025-09-07T06:09:28.2710048Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:28.2710855Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:28.2711628Z  # embedded in the tag name: ciflow// 2025-09-07T06:09:28.2712189Z  2025-09-07T06:09:28.2712568Z  gh = get_gh_client(github_token) 2025-09-07T06:09:28.2713065Z  2025-09-07T06:09:28.2713533Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:28.2714174Z  split_tag = ref_name.split("/") 2025-09-07T06:09:28.2715377Z  if ( 2025-09-07T06:09:28.2715806Z  len(split_tag) == 3 2025-09-07T06:09:28.2716329Z  and split_tag[0] == "ciflow" 2025-09-07T06:09:28.2716864Z  and split_tag[2].isnumeric() 2025-09-07T06:09:28.2717362Z  ): 2025-09-07T06:09:28.2717773Z  pr_number = split_tag[2] 2025-09-07T06:09:28.2718273Z  try: 2025-09-07T06:09:28.2718739Z  repository = gh.get_repo(repo) 2025-09-07T06:09:28.2719544Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:28.2720170Z  except Exception as e: 2025-09-07T06:09:28.2720709Z  raise Exception( # noqa: TRY002 2025-09-07T06:09:28.2721391Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:28.2722028Z  ) from e 2025-09-07T06:09:28.2722604Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:28.2723316Z  # In all other cases, return the original input username 2025-09-07T06:09:28.2723908Z  return username 2025-09-07T06:09:28.2724326Z  2025-09-07T06:09:28.2724783Z  2025-09-07T06:09:28.2725213Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:28.2725740Z  """ 2025-09-07T06:09:28.2726401Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:28.2727165Z  """ 2025-09-07T06:09:28.2727725Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:28.2728382Z  2025-09-07T06:09:28.2728713Z  2025-09-07T06:09:28.2729107Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:28.2729601Z  try: 2025-09-07T06:09:28.2730014Z  data = yaml.safe_load(yaml_text) 2025-09-07T06:09:28.2730525Z  return data 2025-09-07T06:09:28.2730980Z  except yaml.YAMLError: 2025-09-07T06:09:28.2731497Z  log.exception("Error loading YAML") 2025-09-07T06:09:28.2732021Z  raise 2025-09-07T06:09:28.2732410Z  2025-09-07T06:09:28.2732741Z  2025-09-07T06:09:28.2733350Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:28.2734089Z  """ 2025-09-07T06:09:28.2735184Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:28.2735963Z  2025-09-07T06:09:28.2736507Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.2737271Z  and the text below is the list of opted in users. 2025-09-07T06:09:28.2737822Z  2025-09-07T06:09:28.2738389Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:28.2739071Z  """ 2025-09-07T06:09:28.2739585Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:28.2740181Z  if len(rollout_state_parts) >= 2: 2025-09-07T06:09:28.2740809Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:28.2741398Z  else: 2025-09-07T06:09:28.2741799Z  return "", rollout_state 2025-09-07T06:09:28.2742277Z  2025-09-07T06:09:28.2742605Z  2025-09-07T06:09:28.2743010Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:28.2743524Z  """ 2025-09-07T06:09:28.2744063Z  Dictionary of users with a list of features they have opted into 2025-09-07T06:09:28.2744837Z  """ 2025-09-07T06:09:28.2745197Z  2025-09-07T06:09:28.2745520Z  2025-09-07T06:09:28.2746041Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:28.2746688Z  """ 2025-09-07T06:09:28.2747396Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T06:09:28.2748197Z  2025-09-07T06:09:28.2748977Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:28.2749944Z  - Example line: "@User1,lf,split_build" 2025-09-07T06:09:28.2750760Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:28.2751378Z  2025-09-07T06:09:28.2751704Z  2025-09-07T06:09:28.2752026Z  """ 2025-09-07T06:09:28.2752411Z  optins = UserOptins() 2025-09-07T06:09:28.2752927Z  for user in user_optin_text.split("\n"): 2025-09-07T06:09:28.2753492Z  user = user.strip("\r\n\t -") 2025-09-07T06:09:28.2754057Z  if not user or not user.startswith("@"): 2025-09-07T06:09:28.2754845Z  # Not a valid user. Skip 2025-09-07T06:09:28.2755357Z  continue 2025-09-07T06:09:28.2755768Z  2025-09-07T06:09:28.2756105Z  if user: 2025-09-07T06:09:28.2756574Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:28.2757264Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:28.2757897Z  2025-09-07T06:09:28.2758244Z  return optins 2025-09-07T06:09:28.2758660Z  2025-09-07T06:09:28.2758980Z  2025-09-07T06:09:28.2759471Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:28.2760080Z  """ 2025-09-07T06:09:28.2760499Z  Check if the experiment name is valid. 2025-09-07T06:09:28.2761023Z  A valid name: 2025-09-07T06:09:28.2761692Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:28.2762613Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:28.2763309Z  - Cannot contain spaces 2025-09-07T06:09:28.2763787Z  """ 2025-09-07T06:09:28.2764139Z  2025-09-07T06:09:28.2764832Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:28.2765599Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:28.2766333Z  2025-09-07T06:09:28.2766685Z  if valid: 2025-09-07T06:09:28.2767089Z  return True 2025-09-07T06:09:28.2767505Z  2025-09-07T06:09:28.2768041Z  log.error( 2025-09-07T06:09:28.2769611Z  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-09-07T06:09:28.2771076Z  ) 2025-09-07T06:09:28.2771435Z  return False 2025-09-07T06:09:28.2771839Z  2025-09-07T06:09:28.2772161Z  2025-09-07T06:09:28.2772668Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:28.2773287Z  """ 2025-09-07T06:09:28.2773901Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:28.2775175Z  """ 2025-09-07T06:09:28.2775561Z  try: 2025-09-07T06:09:28.2775938Z  if settings_text: 2025-09-07T06:09:28.2776687Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:28.2777497Z  # for easy reading 2025-09-07T06:09:28.2778299Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:28.2779163Z  # the backtick character in shell commands. 2025-09-07T06:09:28.2779772Z  backtick = chr(96) # backtick character 2025-09-07T06:09:28.2780436Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:28.2781095Z  settings = load_yaml(settings_text) 2025-09-07T06:09:28.2781597Z  2025-09-07T06:09:28.2782350Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:28.2783069Z  experiments = {} 2025-09-07T06:09:28.2783527Z  2025-09-07T06:09:28.2784077Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:28.2785169Z  if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:28.2786244Z  # 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-09-07T06:09:28.2787249Z  continue 2025-09-07T06:09:28.2787700Z  2025-09-07T06:09:28.2788069Z  valid_settings = {} 2025-09-07T06:09:28.2788600Z  for setting in exp_settings: 2025-09-07T06:09:28.2789174Z  if setting not in Experiment._fields: 2025-09-07T06:09:28.2789752Z  log.warning( 2025-09-07T06:09:28.2790469Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:28.2791151Z  ) 2025-09-07T06:09:28.2791601Z  else: 2025-09-07T06:09:28.2792146Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:28.2792698Z  2025-09-07T06:09:28.2793165Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:28.2793793Z  return Settings(experiments) 2025-09-07T06:09:28.2794289Z  2025-09-07T06:09:28.2795016Z  except Exception: 2025-09-07T06:09:28.2795535Z  log.exception("Failed to parse settings") 2025-09-07T06:09:28.2796067Z  2025-09-07T06:09:28.2796421Z  return Settings() 2025-09-07T06:09:28.2796869Z  2025-09-07T06:09:28.2797196Z  2025-09-07T06:09:28.2797776Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:28.2798341Z  """ 2025-09-07T06:09:28.2798798Z  Parse settings, if any, from the rollout state. 2025-09-07T06:09:28.2799340Z  2025-09-07T06:09:28.2799872Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.2800628Z  and the text below is the list of opted in users. 2025-09-07T06:09:28.2801168Z  2025-09-07T06:09:28.2801757Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:28.2802465Z  """ 2025-09-07T06:09:28.2803035Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.2803790Z  return parse_settings_from_text(settings_text) 2025-09-07T06:09:28.2804320Z  2025-09-07T06:09:28.2804786Z  2025-09-07T06:09:28.2805232Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:28.2805791Z  """ 2025-09-07T06:09:28.2806197Z  Parse users from the rollout state. 2025-09-07T06:09:28.2806707Z  2025-09-07T06:09:28.2807033Z  """ 2025-09-07T06:09:28.2807579Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.2808321Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:28.2808999Z  2025-09-07T06:09:28.2809376Z  2025-09-07T06:09:28.2809992Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.2810723Z  """ 2025-09-07T06:09:28.2811165Z  Check if a user is opted into an experiment 2025-09-07T06:09:28.2811691Z  """ 2025-09-07T06:09:28.2812162Z  return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:28.2812952Z  2025-09-07T06:09:28.2813283Z  2025-09-07T06:09:28.2813905Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.2814776Z  """ 2025-09-07T06:09:28.2815253Z  Check if a user explicitly opted out of an experiment 2025-09-07T06:09:28.2815822Z  """ 2025-09-07T06:09:28.2816343Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:28.2817039Z  experiment_optout = "-" + experiment_name 2025-09-07T06:09:28.2817693Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:28.2818283Z  return False 2025-09-07T06:09:28.2818705Z  2025-09-07T06:09:28.2819159Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:28.2819753Z  log.warning( 2025-09-07T06:09:28.2820564Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:28.2821405Z  ) 2025-09-07T06:09:28.2821771Z  2025-09-07T06:09:28.2822107Z  return True 2025-09-07T06:09:28.2822505Z  2025-09-07T06:09:28.2822844Z  2025-09-07T06:09:28.2823211Z def get_runner_prefix( 2025-09-07T06:09:28.2823665Z  rollout_state: str, 2025-09-07T06:09:28.2824155Z  workflow_requestors: Iterable[str], 2025-09-07T06:09:28.2825040Z  branch: str, 2025-09-07T06:09:28.2825585Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.2826266Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.2826846Z  is_canary: bool = False, 2025-09-07T06:09:28.2827309Z ) -> str: 2025-09-07T06:09:28.2827755Z  settings = parse_settings(rollout_state) 2025-09-07T06:09:28.2828347Z  user_optins = parse_users(rollout_state) 2025-09-07T06:09:28.2828861Z  2025-09-07T06:09:28.2829351Z  fleet_prefix = "" 2025-09-07T06:09:28.2829809Z  prefixes = [] 2025-09-07T06:09:28.2830485Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:28.2831420Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:28.2832115Z  log.info( 2025-09-07T06:09:28.2832808Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:28.2833522Z  ) 2025-09-07T06:09:28.2833925Z  continue 2025-09-07T06:09:28.2834340Z  2025-09-07T06:09:28.2834940Z  if opt_out_experiments: 2025-09-07T06:09:28.2835506Z  if experiment_name in opt_out_experiments: 2025-09-07T06:09:28.2836138Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:28.2836728Z  log.info( 2025-09-07T06:09:28.2837640Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:28.2838574Z  ) 2025-09-07T06:09:28.2838996Z  continue 2025-09-07T06:09:28.2839426Z  2025-09-07T06:09:28.2839860Z  if eligible_experiments: 2025-09-07T06:09:28.2840436Z  if experiment_name not in eligible_experiments: 2025-09-07T06:09:28.2841077Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:28.2841617Z  log.info( 2025-09-07T06:09:28.2842395Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:28.2843198Z  ) 2025-09-07T06:09:28.2843795Z  continue 2025-09-07T06:09:28.2844314Z  elif not experiment_settings.default: 2025-09-07T06:09:28.2844972Z  log.info( 2025-09-07T06:09:28.2845660Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:28.2846381Z  ) 2025-09-07T06:09:28.2846780Z  continue 2025-09-07T06:09:28.2847194Z  2025-09-07T06:09:28.2847654Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:28.2848266Z  opted_out_users = [ 2025-09-07T06:09:28.2848737Z  requestor 2025-09-07T06:09:28.2849229Z  for requestor in workflow_requestors 2025-09-07T06:09:28.2849892Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.2850510Z  ] 2025-09-07T06:09:28.2850889Z  2025-09-07T06:09:28.2851243Z  if opted_out_users: 2025-09-07T06:09:28.2851717Z  log.info( 2025-09-07T06:09:28.2852359Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:28.2853032Z  ) 2025-09-07T06:09:28.2853427Z  continue 2025-09-07T06:09:28.2853840Z  2025-09-07T06:09:28.2854302Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:28.2854989Z  opted_in_users = [ 2025-09-07T06:09:28.2855460Z  requestor 2025-09-07T06:09:28.2855946Z  for requestor in workflow_requestors 2025-09-07T06:09:28.2856610Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.2857206Z  ] 2025-09-07T06:09:28.2857573Z  2025-09-07T06:09:28.2857922Z  enabled = False 2025-09-07T06:09:28.2858387Z  if opted_in_users: 2025-09-07T06:09:28.2859010Z  log.info( 2025-09-07T06:09:28.2859634Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:28.2860302Z  ) 2025-09-07T06:09:28.2860698Z  enabled = True 2025-09-07T06:09:28.2861145Z  2025-09-07T06:09:28.2861548Z  elif experiment_settings.rollout_perc: 2025-09-07T06:09:28.2862367Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:28.2863273Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:28.2863905Z  log.info( 2025-09-07T06:09:28.2865074Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:28.2865982Z  ) 2025-09-07T06:09:28.2866421Z  enabled = True 2025-09-07T06:09:28.2866893Z  2025-09-07T06:09:28.2867238Z  if enabled: 2025-09-07T06:09:28.2867697Z  label = experiment_name 2025-09-07T06:09:28.2868259Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:28.2869071Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:28.2869918Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:28.2870671Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:28.2871315Z  if is_canary: 2025-09-07T06:09:28.2871822Z  label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:28.2872399Z  fleet_prefix = label 2025-09-07T06:09:28.2873030Z  else: 2025-09-07T06:09:28.2873492Z  prefixes.append(label) 2025-09-07T06:09:28.2873980Z  2025-09-07T06:09:28.2874345Z  if len(prefixes) > 1: 2025-09-07T06:09:28.2875054Z  log.error( 2025-09-07T06:09:28.2876076Z  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-09-07T06:09:28.2877134Z  ) 2025-09-07T06:09:28.2877537Z  prefixes = prefixes[:1] 2025-09-07T06:09:28.2878061Z  2025-09-07T06:09:28.2878439Z  # Fleet always comes first 2025-09-07T06:09:28.2878933Z  if fleet_prefix: 2025-09-07T06:09:28.2879415Z  prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:28.2879914Z  2025-09-07T06:09:28.2880363Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:28.2880925Z  2025-09-07T06:09:28.2881263Z  2025-09-07T06:09:28.2881889Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:28.2882641Z  """ 2025-09-07T06:09:28.2883238Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:28.2883922Z  2025-09-07T06:09:28.2884606Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:28.2885288Z  """ 2025-09-07T06:09:28.2885695Z  gh = get_gh_client(github_token) 2025-09-07T06:09:28.2886238Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:28.2886883Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:28.2887463Z  2025-09-07T06:09:28.2887790Z  2025-09-07T06:09:28.2888375Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:28.2889242Z  for _ in range(num_retries): 2025-09-07T06:09:28.2889728Z  try: 2025-09-07T06:09:28.2890175Z  req = Request(url=url, headers=headers) 2025-09-07T06:09:28.2890837Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:28.2891471Z  return json.loads(content) 2025-09-07T06:09:28.2892022Z  except Exception as e: 2025-09-07T06:09:28.2892586Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:28.2893118Z  2025-09-07T06:09:28.2893686Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:28.2894379Z  return {} 2025-09-07T06:09:28.2894880Z  2025-09-07T06:09:28.2895203Z  2025-09-07T06:09:28.2895537Z @cache 2025-09-07T06:09:28.2896171Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:28.2896892Z  """ 2025-09-07T06:09:28.2897298Z  Dynamically get PR information 2025-09-07T06:09:28.2897778Z  """ 2025-09-07T06:09:28.2898291Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:28.2898902Z  headers = { 2025-09-07T06:09:28.2899387Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:28.2900001Z  "Authorization": f"token {github_token}", 2025-09-07T06:09:28.2900516Z  } 2025-09-07T06:09:28.2900964Z  json_response: dict[str, Any] = download_json( 2025-09-07T06:09:28.2901566Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:28.2902105Z  headers=headers, 2025-09-07T06:09:28.2902544Z  ) 2025-09-07T06:09:28.2902897Z  2025-09-07T06:09:28.2903244Z  if not json_response: 2025-09-07T06:09:28.2903988Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:28.2904950Z  return {} 2025-09-07T06:09:28.2905402Z  2025-09-07T06:09:28.2905764Z  return json_response 2025-09-07T06:09:28.2906211Z  2025-09-07T06:09:28.2906544Z  2025-09-07T06:09:28.2907120Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:28.2907830Z  """ 2025-09-07T06:09:28.2908368Z  Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:28.2909002Z  """ 2025-09-07T06:09:28.2909498Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:28.2910082Z  return { 2025-09-07T06:09:28.2910681Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:28.2911342Z  } 2025-09-07T06:09:28.2911700Z  2025-09-07T06:09:28.2912031Z  2025-09-07T06:09:28.2912386Z def main() -> None: 2025-09-07T06:09:28.2912832Z  args = parse_args() 2025-09-07T06:09:28.2913266Z  2025-09-07T06:09:28.2913683Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:28.2914198Z  2025-09-07T06:09:28.2914774Z  # Check if the PR is opt-out 2025-09-07T06:09:28.2915280Z  if args.pr_number: 2025-09-07T06:09:28.2915955Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:28.2916679Z  if OPT_OUT_LABEL in labels: 2025-09-07T06:09:28.2917169Z  log.info( 2025-09-07T06:09:28.2917869Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:28.2918596Z  ) 2025-09-07T06:09:28.2919188Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.2919897Z  sys.exit() 2025-09-07T06:09:28.2920503Z  2025-09-07T06:09:28.2920839Z  try: 2025-09-07T06:09:28.2921304Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:28.2922006Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:28.2922628Z  ) 2025-09-07T06:09:28.2922999Z  2025-09-07T06:09:28.2923388Z  username = get_potential_pr_author( 2025-09-07T06:09:28.2923919Z  args.github_token, 2025-09-07T06:09:28.2924410Z  args.github_repo, 2025-09-07T06:09:28.2925027Z  args.github_actor, 2025-09-07T06:09:28.2925530Z  args.github_ref_type, 2025-09-07T06:09:28.2926027Z  args.github_branch, 2025-09-07T06:09:28.2926500Z  ) 2025-09-07T06:09:28.2926860Z  2025-09-07T06:09:28.2927349Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:28.2927932Z  2025-09-07T06:09:28.2928346Z  runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:28.2928893Z  rollout_state, 2025-09-07T06:09:28.2929411Z  (args.github_issue_owner, username), 2025-09-07T06:09:28.2929952Z  args.github_branch, 2025-09-07T06:09:28.2930461Z  args.eligible_experiments, 2025-09-07T06:09:28.2930999Z  args.opt_out_experiments, 2025-09-07T06:09:28.2931496Z  is_canary, 2025-09-07T06:09:28.2931931Z  ) 2025-09-07T06:09:28.2932292Z  2025-09-07T06:09:28.2932658Z  except Exception as e: 2025-09-07T06:09:28.2933134Z  log.error( 2025-09-07T06:09:28.2933827Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:28.2934909Z  ) 2025-09-07T06:09:28.2935285Z  2025-09-07T06:09:28.2935799Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.2936429Z  2025-09-07T06:09:28.2936766Z  2025-09-07T06:09:28.2937245Z if __name__ == "__main__": 2025-09-07T06:09:28.2937709Z  main() 2025-09-07T06:09:28.2938083Z  2025-09-07T06:09:28.2938403Z EOF 2025-09-07T06:09:28.2938746Z  2025-09-07T06:09:28.2939108Z cat runner_determinator.py 2025-09-07T06:09:28.3134079Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:28.3135248Z env: 2025-09-07T06:09:28.3135944Z GITHUB_TOKEN: *** 2025-09-07T06:09:28.3136347Z ISSUE_NUMBER: 5132 2025-09-07T06:09:28.3136773Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:28.3137258Z ISSUE_OWNER: 2025-09-07T06:09:28.3137626Z CHECK_EXPERIMENTS: 2025-09-07T06:09:28.3138033Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:28.3138441Z PR_NUMBER: 2025-09-07T06:09:28.3138803Z ##[endgroup] 2025-09-07T06:09:28.3386006Z # flake8: noqa: G004 2025-09-07T06:09:28.3386534Z 2025-09-07T06:09:28.3387192Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:28.3388530Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:28.3389322Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:28.3389735Z 2025-09-07T06:09:28.3389891Z """ 2025-09-07T06:09:28.3390442Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:28.3391269Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:28.3392232Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:28.3392993Z of which runners should be used to run which job. 2025-09-07T06:09:28.3393364Z 2025-09-07T06:09:28.3393727Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:28.3395038Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:28.3395926Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:28.3396572Z list, defined. 2025-09-07T06:09:28.3396791Z 2025-09-07T06:09:28.3397137Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:28.3397977Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:28.3398746Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:28.3399287Z 2025-09-07T06:09:28.3399658Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:28.3400464Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:28.3401147Z experiments which the user could be opted in to. 2025-09-07T06:09:28.3401517Z 2025-09-07T06:09:28.3401710Z The user list has the following rules: 2025-09-07T06:09:28.3402063Z 2025-09-07T06:09:28.3402361Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:28.3403182Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:28.3403901Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:28.3404269Z 2025-09-07T06:09:28.3404601Z Example config: 2025-09-07T06:09:28.3405090Z # A list of experiments that can be opted into. 2025-09-07T06:09:28.3405715Z # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:28.3406312Z # Expected syntax is: 2025-09-07T06:09:28.3406912Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:28.3407820Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:28.3408388Z 2025-09-07T06:09:28.3408550Z experiments: 2025-09-07T06:09:28.3408913Z lf: 2025-09-07T06:09:28.3409285Z rollout_percent: 25 2025-09-07T06:09:28.3409944Z all_branches: false 2025-09-07T06:09:28.3410362Z default: true 2025-09-07T06:09:28.3410748Z --- 2025-09-07T06:09:28.3410935Z 2025-09-07T06:09:28.3411091Z # Opt-ins: 2025-09-07T06:09:28.3411633Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:28.3412423Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:28.3413132Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:28.3413739Z # Experiments should be from the above list. 2025-09-07T06:09:28.3414105Z 2025-09-07T06:09:28.3414275Z @User1,-lf,split_build 2025-09-07T06:09:28.3414865Z @User2,lf 2025-09-07T06:09:28.3415219Z @User3,split_build 2025-09-07T06:09:28.3415595Z """ 2025-09-07T06:09:28.3415772Z 2025-09-07T06:09:28.3415932Z import json 2025-09-07T06:09:28.3416282Z import logging 2025-09-07T06:09:28.3416641Z import os 2025-09-07T06:09:28.3416979Z import random 2025-09-07T06:09:28.3417341Z import re 2025-09-07T06:09:28.3417785Z import sys 2025-09-07T06:09:28.3418166Z from argparse import ArgumentParser 2025-09-07T06:09:28.3418653Z from collections.abc import Iterable 2025-09-07T06:09:28.3419145Z from functools import cache 2025-09-07T06:09:28.3419582Z from logging import LogRecord 2025-09-07T06:09:28.3420039Z from typing import Any, NamedTuple 2025-09-07T06:09:28.3420536Z from urllib.request import Request, urlopen 2025-09-07T06:09:28.3420887Z 2025-09-07T06:09:28.3421041Z import yaml 2025-09-07T06:09:28.3421409Z from github import Auth, Github 2025-09-07T06:09:28.3421859Z from github.Issue import Issue 2025-09-07T06:09:28.3422156Z 2025-09-07T06:09:28.3422162Z 2025-09-07T06:09:28.3422374Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:28.3422996Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:28.3423843Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:28.3424376Z 2025-09-07T06:09:28.3425714Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:28.3427056Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:28.3428007Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:28.3428936Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:28.3429545Z 2025-09-07T06:09:28.3429890Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:28.3430469Z 2025-09-07T06:09:28.3430788Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:28.3431541Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:28.3432035Z 2025-09-07T06:09:28.3432051Z 2025-09-07T06:09:28.3432372Z class Experiment(NamedTuple): 2025-09-07T06:09:28.3433226Z rollout_perc: float = ( 2025-09-07T06:09:28.3434206Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:28.3436391Z ) 2025-09-07T06:09:28.3437047Z all_branches: bool = ( 2025-09-07T06:09:28.3438065Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:28.3439270Z ) 2025-09-07T06:09:28.3439898Z default: bool = ( 2025-09-07T06:09:28.3440847Z True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:28.3441945Z ) 2025-09-07T06:09:28.3442306Z 2025-09-07T06:09:28.3442613Z # Add more fields as needed 2025-09-07T06:09:28.3443134Z 2025-09-07T06:09:28.3443145Z 2025-09-07T06:09:28.3443460Z class Settings(NamedTuple): 2025-09-07T06:09:28.3444176Z """ 2025-09-07T06:09:28.3446519Z Settings for the experiments that can be opted into. 2025-09-07T06:09:28.3447502Z """ 2025-09-07T06:09:28.3447854Z 2025-09-07T06:09:28.3448189Z experiments: dict[str, Experiment] = {} 2025-09-07T06:09:28.3448790Z 2025-09-07T06:09:28.3448799Z 2025-09-07T06:09:28.3449149Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:28.3450149Z """Color codes the log messages based on the log level""" 2025-09-07T06:09:28.3450865Z 2025-09-07T06:09:28.3451133Z COLORS = { 2025-09-07T06:09:28.3451745Z "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:28.3452850Z "ERROR": "\033[31m", # Red 2025-09-07T06:09:28.3453656Z "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:28.3454671Z "INFO": "\033[0m", # Reset 2025-09-07T06:09:28.3455502Z "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:28.3456322Z } 2025-09-07T06:09:28.3456670Z 2025-09-07T06:09:28.3457048Z def format(self, record: LogRecord) -> str: 2025-09-07T06:09:28.3458304Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:28.3459619Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:28.3460610Z return super().format(record) 2025-09-07T06:09:28.3461192Z 2025-09-07T06:09:28.3461203Z 2025-09-07T06:09:28.3461526Z handler = logging.StreamHandler() 2025-09-07T06:09:28.3462714Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:28.3463705Z 2025-09-07T06:09:28.3464137Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:28.3465411Z log.addHandler(handler) 2025-09-07T06:09:28.3466151Z log.setLevel(logging.INFO) 2025-09-07T06:09:28.3466583Z 2025-09-07T06:09:28.3466592Z 2025-09-07T06:09:28.3466978Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:28.3467525Z """ 2025-09-07T06:09:28.3468008Z Defines outputs of the github action that invokes this script 2025-09-07T06:09:28.3468590Z """ 2025-09-07T06:09:28.3468940Z if not GITHUB_OUTPUT: 2025-09-07T06:09:28.3469944Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T06:09:28.3470974Z log.warning( 2025-09-07T06:09:28.3471833Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T06:09:28.3472688Z ) 2025-09-07T06:09:28.3483326Z print(f"::set-output name={key}::{value}") 2025-09-07T06:09:28.3483883Z return 2025-09-07T06:09:28.3484117Z 2025-09-07T06:09:28.3484753Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:28.3485357Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:28.3485899Z f.write(f"{key}={value}\n") 2025-09-07T06:09:28.3486205Z 2025-09-07T06:09:28.3486212Z 2025-09-07T06:09:28.3486504Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:28.3487088Z return frozenset( 2025-09-07T06:09:28.3487659Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:28.3488296Z ) 2025-09-07T06:09:28.3488482Z 2025-09-07T06:09:28.3488490Z 2025-09-07T06:09:28.3488661Z def parse_args() -> Any: 2025-09-07T06:09:28.3489186Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:28.3489980Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:28.3490688Z parser.add_argument( 2025-09-07T06:09:28.3491111Z "--github-issue-repo", 2025-09-07T06:09:28.3491554Z type=str, 2025-09-07T06:09:28.3491928Z required=False, 2025-09-07T06:09:28.3492357Z default="pytorch/test-infra", 2025-09-07T06:09:28.3492854Z help="GitHub repo to get the issue", 2025-09-07T06:09:28.3493334Z ) 2025-09-07T06:09:28.3493669Z parser.add_argument( 2025-09-07T06:09:28.3494081Z "--github-repo", 2025-09-07T06:09:28.3494759Z type=str, 2025-09-07T06:09:28.3495171Z required=True, 2025-09-07T06:09:28.3495602Z help="GitHub repo where CI is running", 2025-09-07T06:09:28.3496080Z ) 2025-09-07T06:09:28.3496425Z parser.add_argument( 2025-09-07T06:09:28.3496982Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:28.3497599Z ) 2025-09-07T06:09:28.3497939Z parser.add_argument( 2025-09-07T06:09:28.3498516Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:28.3499138Z ) 2025-09-07T06:09:28.3499638Z parser.add_argument( 2025-09-07T06:09:28.3500240Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:28.3500883Z ) 2025-09-07T06:09:28.3501225Z parser.add_argument( 2025-09-07T06:09:28.3501830Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:28.3502540Z ) 2025-09-07T06:09:28.3502921Z parser.add_argument( 2025-09-07T06:09:28.3503341Z "--github-ref-type", 2025-09-07T06:09:28.3503758Z type=str, 2025-09-07T06:09:28.3504130Z required=True, 2025-09-07T06:09:28.3504906Z help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:28.3505445Z ) 2025-09-07T06:09:28.3505790Z parser.add_argument( 2025-09-07T06:09:28.3506215Z "--eligible-experiments", 2025-09-07T06:09:28.3506695Z type=_str_comma_separated_to_set, 2025-09-07T06:09:28.3507172Z required=False, 2025-09-07T06:09:28.3507567Z default="", 2025-09-07T06:09:28.3508368Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:28.3509235Z ) 2025-09-07T06:09:28.3509572Z parser.add_argument( 2025-09-07T06:09:28.3510003Z "--opt-out-experiments", 2025-09-07T06:09:28.3510470Z type=_str_comma_separated_to_set, 2025-09-07T06:09:28.3510952Z required=False, 2025-09-07T06:09:28.3511342Z default="", 2025-09-07T06:09:28.3511702Z help=( 2025-09-07T06:09:28.3512323Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:28.3513367Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:28.3514151Z ), 2025-09-07T06:09:28.3514697Z ) 2025-09-07T06:09:28.3515167Z parser.add_argument( 2025-09-07T06:09:28.3515591Z "--pr-number", 2025-09-07T06:09:28.3515975Z type=str, 2025-09-07T06:09:28.3516358Z required=False, 2025-09-07T06:09:28.3516745Z default="", 2025-09-07T06:09:28.3517348Z help="the optional PR number where this is run", 2025-09-07T06:09:28.3517878Z ) 2025-09-07T06:09:28.3518061Z 2025-09-07T06:09:28.3518250Z return parser.parse_args() 2025-09-07T06:09:28.3518534Z 2025-09-07T06:09:28.3518541Z 2025-09-07T06:09:28.3518930Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.3519631Z auth = Auth.Token(github_token) 2025-09-07T06:09:28.3520107Z return Github(auth=auth) 2025-09-07T06:09:28.3520382Z 2025-09-07T06:09:28.3520389Z 2025-09-07T06:09:28.3520823Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.3521556Z repo = gh.get_repo(repo) 2025-09-07T06:09:28.3522010Z return repo.get_issue(number=issue_num) 2025-09-07T06:09:28.3522349Z 2025-09-07T06:09:28.3522355Z 2025-09-07T06:09:28.3522532Z def get_potential_pr_author( 2025-09-07T06:09:28.3523132Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:28.3523761Z ) -> str: 2025-09-07T06:09:28.3524238Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:28.3525136Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:28.3525823Z # embedded in the tag name: ciflow// 2025-09-07T06:09:28.3526466Z 2025-09-07T06:09:28.3526654Z gh = get_gh_client(github_token) 2025-09-07T06:09:28.3526977Z 2025-09-07T06:09:28.3527230Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:28.3527808Z split_tag = ref_name.split("/") 2025-09-07T06:09:28.3528267Z if ( 2025-09-07T06:09:28.3528628Z len(split_tag) == 3 2025-09-07T06:09:28.3529071Z and split_tag[0] == "ciflow" 2025-09-07T06:09:28.3529561Z and split_tag[2].isnumeric() 2025-09-07T06:09:28.3530014Z ): 2025-09-07T06:09:28.3530522Z pr_number = split_tag[2] 2025-09-07T06:09:28.3530975Z try: 2025-09-07T06:09:28.3531385Z repository = gh.get_repo(repo) 2025-09-07T06:09:28.3531951Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:28.3532510Z except Exception as e: 2025-09-07T06:09:28.3532997Z raise Exception( # noqa: TRY002 2025-09-07T06:09:28.3533611Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:28.3534209Z ) from e 2025-09-07T06:09:28.3534912Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:28.3535563Z # In all other cases, return the original input username 2025-09-07T06:09:28.3536102Z return username 2025-09-07T06:09:28.3536332Z 2025-09-07T06:09:28.3536339Z 2025-09-07T06:09:28.3536548Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:28.3537043Z """ 2025-09-07T06:09:28.3537647Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:28.3538391Z """ 2025-09-07T06:09:28.3538900Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:28.3539378Z 2025-09-07T06:09:28.3539391Z 2025-09-07T06:09:28.3539578Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:28.3540028Z try: 2025-09-07T06:09:28.3596496Z data = yaml.safe_load(yaml_text) 2025-09-07T06:09:28.3597458Z return data 2025-09-07T06:09:28.3598086Z except yaml.YAMLError: 2025-09-07T06:09:28.3598647Z log.exception("Error loading YAML") 2025-09-07T06:09:28.3599141Z raise 2025-09-07T06:09:28.3599352Z 2025-09-07T06:09:28.3599359Z 2025-09-07T06:09:28.3599760Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:28.3600455Z """ 2025-09-07T06:09:28.3601038Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:28.3601620Z 2025-09-07T06:09:28.3602200Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.3602926Z and the text below is the list of opted in users. 2025-09-07T06:09:28.3603297Z 2025-09-07T06:09:28.3603658Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:28.3604299Z """ 2025-09-07T06:09:28.3605058Z rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:28.3605623Z if len(rollout_state_parts) >= 2: 2025-09-07T06:09:28.3606182Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:28.3606727Z else: 2025-09-07T06:09:28.3607081Z return "", rollout_state 2025-09-07T06:09:28.3607362Z 2025-09-07T06:09:28.3607370Z 2025-09-07T06:09:28.3607559Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:28.3608027Z """ 2025-09-07T06:09:28.3608511Z Dictionary of users with a list of features they have opted into 2025-09-07T06:09:28.3609097Z """ 2025-09-07T06:09:28.3609284Z 2025-09-07T06:09:28.3609291Z 2025-09-07T06:09:28.3609626Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:28.3610223Z """ 2025-09-07T06:09:28.3611002Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T06:09:28.3611642Z 2025-09-07T06:09:28.3612222Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T06:09:28.3613138Z - Example line: "@User1,lf,split_build" 2025-09-07T06:09:28.3613769Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:28.3614210Z 2025-09-07T06:09:28.3614217Z 2025-09-07T06:09:28.3614366Z """ 2025-09-07T06:09:28.3614955Z optins = UserOptins() 2025-09-07T06:09:28.3615417Z for user in user_optin_text.split("\n"): 2025-09-07T06:09:28.3615940Z user = user.strip("\r\n\t -") 2025-09-07T06:09:28.3616662Z if not user or not user.startswith("@"): 2025-09-07T06:09:28.3617181Z # Not a valid user. Skip 2025-09-07T06:09:28.3617635Z continue 2025-09-07T06:09:28.3617865Z 2025-09-07T06:09:28.3618013Z if user: 2025-09-07T06:09:28.3618424Z usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:28.3619065Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:28.3619527Z 2025-09-07T06:09:28.3619683Z return optins 2025-09-07T06:09:28.3619900Z 2025-09-07T06:09:28.3619906Z 2025-09-07T06:09:28.3620179Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:28.3620734Z """ 2025-09-07T06:09:28.3621104Z Check if the experiment name is valid. 2025-09-07T06:09:28.3621591Z A valid name: 2025-09-07T06:09:28.3622192Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:28.3623056Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:28.3623730Z - Cannot contain spaces 2025-09-07T06:09:28.3624158Z """ 2025-09-07T06:09:28.3624346Z 2025-09-07T06:09:28.3625194Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:28.3625871Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:28.3626278Z 2025-09-07T06:09:28.3626428Z if valid: 2025-09-07T06:09:28.3626782Z return True 2025-09-07T06:09:28.3627001Z 2025-09-07T06:09:28.3627153Z log.error( 2025-09-07T06:09:28.3628501Z 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-09-07T06:09:28.3629994Z ) 2025-09-07T06:09:28.3630322Z return False 2025-09-07T06:09:28.3630544Z 2025-09-07T06:09:28.3630550Z 2025-09-07T06:09:28.3630834Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:28.3631411Z """ 2025-09-07T06:09:28.3632115Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:28.3632785Z """ 2025-09-07T06:09:28.3633103Z try: 2025-09-07T06:09:28.3633450Z if settings_text: 2025-09-07T06:09:28.3634109Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T06:09:28.3635106Z # for easy reading 2025-09-07T06:09:28.3635836Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:28.3636658Z # the backtick character in shell commands. 2025-09-07T06:09:28.3637218Z backtick = chr(96) # backtick character 2025-09-07T06:09:28.3637829Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:28.3638440Z settings = load_yaml(settings_text) 2025-09-07T06:09:28.3638781Z 2025-09-07T06:09:28.3639162Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:28.3639871Z experiments = {} 2025-09-07T06:09:28.3640141Z 2025-09-07T06:09:28.3640505Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:28.3641225Z if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:28.3642240Z # 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-09-07T06:09:28.3643194Z continue 2025-09-07T06:09:28.3643451Z 2025-09-07T06:09:28.3643626Z valid_settings = {} 2025-09-07T06:09:28.3644099Z for setting in exp_settings: 2025-09-07T06:09:28.3644797Z if setting not in Experiment._fields: 2025-09-07T06:09:28.3645313Z log.warning( 2025-09-07T06:09:28.3645959Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:28.3646790Z ) 2025-09-07T06:09:28.3647187Z else: 2025-09-07T06:09:28.3647665Z valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:28.3648054Z 2025-09-07T06:09:28.3648305Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:28.3648891Z return Settings(experiments) 2025-09-07T06:09:28.3649214Z 2025-09-07T06:09:28.3649383Z except Exception: 2025-09-07T06:09:28.3649834Z log.exception("Failed to parse settings") 2025-09-07T06:09:28.3650189Z 2025-09-07T06:09:28.3650356Z return Settings() 2025-09-07T06:09:28.3650593Z 2025-09-07T06:09:28.3650599Z 2025-09-07T06:09:28.3650826Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:28.3651352Z """ 2025-09-07T06:09:28.3651748Z Parse settings, if any, from the rollout state. 2025-09-07T06:09:28.3652124Z 2025-09-07T06:09:28.3652454Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.3653151Z and the text below is the list of opted in users. 2025-09-07T06:09:28.3653527Z 2025-09-07T06:09:28.3653899Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:28.3654788Z """ 2025-09-07T06:09:28.3655342Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.3656047Z return parse_settings_from_text(settings_text) 2025-09-07T06:09:28.3656414Z 2025-09-07T06:09:28.3656421Z 2025-09-07T06:09:28.3656649Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:28.3657169Z """ 2025-09-07T06:09:28.3657526Z Parse users from the rollout state. 2025-09-07T06:09:28.3657854Z 2025-09-07T06:09:28.3658004Z """ 2025-09-07T06:09:28.3658495Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.3659160Z return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:28.3659537Z 2025-09-07T06:09:28.3659551Z 2025-09-07T06:09:28.3660124Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.3660811Z """ 2025-09-07T06:09:28.3661195Z Check if a user is opted into an experiment 2025-09-07T06:09:28.3661675Z """ 2025-09-07T06:09:28.3662087Z return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:28.3662468Z 2025-09-07T06:09:28.3662474Z 2025-09-07T06:09:28.3662868Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.3663550Z """ 2025-09-07T06:09:28.3663997Z Check if a user explicitly opted out of an experiment 2025-09-07T06:09:28.3664765Z """ 2025-09-07T06:09:28.3665383Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:28.3666018Z experiment_optout = "-" + experiment_name 2025-09-07T06:09:28.3666622Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:28.3667186Z return False 2025-09-07T06:09:28.3667411Z 2025-09-07T06:09:28.3667665Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:28.3668219Z log.warning( 2025-09-07T06:09:28.3668968Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:28.3669771Z ) 2025-09-07T06:09:28.3669956Z 2025-09-07T06:09:28.3670112Z return True 2025-09-07T06:09:28.3670327Z 2025-09-07T06:09:28.3670333Z 2025-09-07T06:09:28.3670496Z def get_runner_prefix( 2025-09-07T06:09:28.3670904Z rollout_state: str, 2025-09-07T06:09:28.3671325Z workflow_requestors: Iterable[str], 2025-09-07T06:09:28.3671801Z branch: str, 2025-09-07T06:09:28.3672240Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.3672850Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.3673411Z is_canary: bool = False, 2025-09-07T06:09:28.3673834Z ) -> str: 2025-09-07T06:09:28.3674386Z settings = parse_settings(rollout_state) 2025-09-07T06:09:28.3675208Z user_optins = parse_users(rollout_state) 2025-09-07T06:09:28.3675548Z 2025-09-07T06:09:28.3675717Z fleet_prefix = "" 2025-09-07T06:09:28.3676101Z prefixes = [] 2025-09-07T06:09:28.3676679Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:28.3677538Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:28.3678183Z log.info( 2025-09-07T06:09:28.3678795Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:28.3679488Z ) 2025-09-07T06:09:28.3679829Z continue 2025-09-07T06:09:28.3680058Z 2025-09-07T06:09:28.3680235Z if opt_out_experiments: 2025-09-07T06:09:28.3680717Z if experiment_name in opt_out_experiments: 2025-09-07T06:09:28.3681355Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:28.3681904Z log.info( 2025-09-07T06:09:28.3682776Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:28.3683680Z ) 2025-09-07T06:09:28.3684043Z continue 2025-09-07T06:09:28.3684291Z 2025-09-07T06:09:28.3684570Z if eligible_experiments: 2025-09-07T06:09:28.3685209Z if experiment_name not in eligible_experiments: 2025-09-07T06:09:28.3685792Z exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:28.3686297Z log.info( 2025-09-07T06:09:28.3687012Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:28.3687787Z ) 2025-09-07T06:09:28.3688146Z continue 2025-09-07T06:09:28.3688573Z elif not experiment_settings.default: 2025-09-07T06:09:28.3689071Z log.info( 2025-09-07T06:09:28.3689826Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:28.3690542Z ) 2025-09-07T06:09:28.3690884Z continue 2025-09-07T06:09:28.3691106Z 2025-09-07T06:09:28.3691367Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:28.3691925Z opted_out_users = [ 2025-09-07T06:09:28.3692336Z requestor 2025-09-07T06:09:28.3692751Z for requestor in workflow_requestors 2025-09-07T06:09:28.3693370Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.3693960Z ] 2025-09-07T06:09:28.3694147Z 2025-09-07T06:09:28.3694314Z if opted_out_users: 2025-09-07T06:09:28.3694986Z log.info( 2025-09-07T06:09:28.3695565Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:28.3696210Z ) 2025-09-07T06:09:28.3696557Z continue 2025-09-07T06:09:28.3696781Z 2025-09-07T06:09:28.3697034Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:28.3697592Z opted_in_users = [ 2025-09-07T06:09:28.3698002Z requestor 2025-09-07T06:09:28.3698414Z for requestor in workflow_requestors 2025-09-07T06:09:28.3699015Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.3699588Z ] 2025-09-07T06:09:28.3699775Z 2025-09-07T06:09:28.3699933Z enabled = False 2025-09-07T06:09:28.3700336Z if opted_in_users: 2025-09-07T06:09:28.3700736Z log.info( 2025-09-07T06:09:28.3701280Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:28.3701904Z ) 2025-09-07T06:09:28.3702258Z enabled = True 2025-09-07T06:09:28.3702512Z 2025-09-07T06:09:28.3702715Z elif experiment_settings.rollout_perc: 2025-09-07T06:09:28.3703480Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:28.3704604Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:28.3705220Z log.info( 2025-09-07T06:09:28.3706017Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:28.3706872Z ) 2025-09-07T06:09:28.3707242Z enabled = True 2025-09-07T06:09:28.3707510Z 2025-09-07T06:09:28.3707671Z if enabled: 2025-09-07T06:09:28.3708051Z label = experiment_name 2025-09-07T06:09:28.3708548Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:28.3709300Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:28.3710102Z # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:28.3710821Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:28.3711446Z if is_canary: 2025-09-07T06:09:28.3711910Z label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:28.3712411Z fleet_prefix = label 2025-09-07T06:09:28.3712860Z else: 2025-09-07T06:09:28.3713246Z prefixes.append(label) 2025-09-07T06:09:28.3713565Z 2025-09-07T06:09:28.3713732Z if len(prefixes) > 1: 2025-09-07T06:09:28.3714133Z log.error( 2025-09-07T06:09:28.3715268Z 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-09-07T06:09:28.3716309Z ) 2025-09-07T06:09:28.3716653Z prefixes = prefixes[:1] 2025-09-07T06:09:28.3716932Z 2025-09-07T06:09:28.3717115Z # Fleet always comes first 2025-09-07T06:09:28.3717539Z if fleet_prefix: 2025-09-07T06:09:28.3717951Z prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:28.3718368Z 2025-09-07T06:09:28.3718886Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:28.3719316Z 2025-09-07T06:09:28.3719322Z 2025-09-07T06:09:28.3719738Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:28.3720454Z """ 2025-09-07T06:09:28.3720986Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:28.3721497Z 2025-09-07T06:09:28.3721863Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:28.3722511Z """ 2025-09-07T06:09:28.3722870Z gh = get_gh_client(github_token) 2025-09-07T06:09:28.3723354Z issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:28.3723936Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:28.3724340Z 2025-09-07T06:09:28.3724347Z 2025-09-07T06:09:28.3724879Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:28.3725588Z for _ in range(num_retries): 2025-09-07T06:09:28.3726030Z try: 2025-09-07T06:09:28.3726415Z req = Request(url=url, headers=headers) 2025-09-07T06:09:28.3727024Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:28.3727606Z return json.loads(content) 2025-09-07T06:09:28.3728093Z except Exception as e: 2025-09-07T06:09:28.3728587Z log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:28.3728956Z 2025-09-07T06:09:28.3729300Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:28.3729947Z return {} 2025-09-07T06:09:28.3730146Z 2025-09-07T06:09:28.3730153Z 2025-09-07T06:09:28.3730295Z @cache 2025-09-07T06:09:28.3730855Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:28.3731543Z """ 2025-09-07T06:09:28.3731904Z Dynamically get PR information 2025-09-07T06:09:28.3732483Z """ 2025-09-07T06:09:28.3732945Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:28.3733531Z headers = { 2025-09-07T06:09:28.3733944Z "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:28.3734619Z "Authorization": f"token {github_token}", 2025-09-07T06:09:28.3735115Z } 2025-09-07T06:09:28.3735510Z json_response: dict[str, Any] = download_json( 2025-09-07T06:09:28.3736052Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:28.3736550Z headers=headers, 2025-09-07T06:09:28.3736933Z ) 2025-09-07T06:09:28.3737117Z 2025-09-07T06:09:28.3737283Z if not json_response: 2025-09-07T06:09:28.3737806Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:28.3738369Z return {} 2025-09-07T06:09:28.3738582Z 2025-09-07T06:09:28.3738761Z return json_response 2025-09-07T06:09:28.3739014Z 2025-09-07T06:09:28.3739019Z 2025-09-07T06:09:28.3739382Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:28.3740062Z """ 2025-09-07T06:09:28.3740536Z Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:28.3741135Z """ 2025-09-07T06:09:28.3741579Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:28.3742129Z return { 2025-09-07T06:09:28.3742667Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:28.3743304Z } 2025-09-07T06:09:28.3743489Z 2025-09-07T06:09:28.3743495Z 2025-09-07T06:09:28.3743656Z def main() -> None: 2025-09-07T06:09:28.3744039Z args = parse_args() 2025-09-07T06:09:28.3744285Z 2025-09-07T06:09:28.3744716Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:28.3745138Z 2025-09-07T06:09:28.3745327Z # Check if the PR is opt-out 2025-09-07T06:09:28.3745769Z if args.pr_number: 2025-09-07T06:09:28.3746363Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:28.3747206Z if OPT_OUT_LABEL in labels: 2025-09-07T06:09:28.3747685Z log.info( 2025-09-07T06:09:28.3748307Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:28.3749005Z ) 2025-09-07T06:09:28.3749492Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.3750102Z sys.exit() 2025-09-07T06:09:28.3750344Z 2025-09-07T06:09:28.3750500Z try: 2025-09-07T06:09:28.3750893Z rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:28.3751534Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:28.3752114Z ) 2025-09-07T06:09:28.3752302Z 2025-09-07T06:09:28.3752492Z username = get_potential_pr_author( 2025-09-07T06:09:28.3752982Z args.github_token, 2025-09-07T06:09:28.3753420Z args.github_repo, 2025-09-07T06:09:28.3753848Z args.github_actor, 2025-09-07T06:09:28.3754294Z args.github_ref_type, 2025-09-07T06:09:28.3754996Z args.github_branch, 2025-09-07T06:09:28.3755408Z ) 2025-09-07T06:09:28.3755595Z 2025-09-07T06:09:28.3755866Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:28.3756272Z 2025-09-07T06:09:28.3756471Z runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:28.3756981Z rollout_state, 2025-09-07T06:09:28.3757418Z (args.github_issue_owner, username), 2025-09-07T06:09:28.3757920Z args.github_branch, 2025-09-07T06:09:28.3758373Z args.eligible_experiments, 2025-09-07T06:09:28.3758870Z args.opt_out_experiments, 2025-09-07T06:09:28.3759331Z is_canary, 2025-09-07T06:09:28.3759708Z ) 2025-09-07T06:09:28.3759891Z 2025-09-07T06:09:28.3760067Z except Exception as e: 2025-09-07T06:09:28.3760467Z log.error( 2025-09-07T06:09:28.3761079Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:28.3761941Z ) 2025-09-07T06:09:28.3762143Z 2025-09-07T06:09:28.3762445Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.3762895Z 2025-09-07T06:09:28.3762901Z 2025-09-07T06:09:28.3763073Z if __name__ == "__main__": 2025-09-07T06:09:28.3763465Z main() 2025-09-07T06:09:28.3763655Z 2025-09-07T06:09:28.3857378Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:28.3858227Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:28.3888802Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:28.3889236Z env: 2025-09-07T06:09:28.3889859Z GITHUB_TOKEN: *** 2025-09-07T06:09:28.3890237Z ISSUE_NUMBER: 5132 2025-09-07T06:09:28.3890651Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:28.3891112Z ISSUE_OWNER: 2025-09-07T06:09:28.3891472Z CHECK_EXPERIMENTS: 2025-09-07T06:09:28.3891867Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:28.3892263Z PR_NUMBER: 2025-09-07T06:09:28.3892618Z ##[endgroup] 2025-09-07T06:09:28.7524052Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T06:09:29.1319181Z Collecting urllib3==1.26.18 2025-09-07T06:09:29.1652739Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T06:09:29.1871363Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.0 MB/s eta 0:00:00 2025-09-07T06:09:29.2131933Z Collecting PyGithub==2.3.0 2025-09-07T06:09:29.2177271Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T06:09:29.2599098Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T06:09:29.2631824Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl.metadata (8.6 kB) 2025-09-07T06:09:29.2678533Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-09-07T06:09:29.2694369Z 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-09-07T06:09:29.2714642Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-09-07T06:09:29.2973649Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T06:09:29.3006932Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T06:09:29.3241133Z 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-09-07T06:09:29.4385090Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:29.4420184Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T06:09:29.5659155Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T06:09:29.5696488Z 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-09-07T06:09:29.5883204Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:29.5917551Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T06:09:29.6147942Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T06:09:29.6207183Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 34.0 MB/s eta 0:00:00 2025-09-07T06:09:29.6238125Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T06:09:29.6296710Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 87.2 MB/s eta 0:00:00 2025-09-07T06:09:29.6333220Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-09-07T06:09:29.6436114Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 102.0 MB/s eta 0:00:00 2025-09-07T06:09:29.6468312Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T06:09:29.6530689Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T06:09:29.6594149Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 104.4 MB/s eta 0:00:00 2025-09-07T06:09:29.6628171Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-09-07T06:09:29.6669258Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 31.4 MB/s eta 0:00:00 2025-09-07T06:09:29.6702374Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T06:09:29.6744853Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 41.9 MB/s eta 0:00:00 2025-09-07T06:09:29.9715887Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T06:09:30.5167432Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-09-07T06:09:30.6119695Z ##[group]Run curr_branch="main" 2025-09-07T06:09:30.6120061Z curr_branch="main" 2025-09-07T06:09:30.6120374Z curr_ref_type="branch" 2025-09-07T06:09:30.6120669Z echo "Current branch is '$curr_branch'" 2025-09-07T06:09:30.6120972Z  2025-09-07T06:09:30.6121202Z python3 runner_determinator.py \ 2025-09-07T06:09:30.6121534Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T06:09:30.6121856Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T06:09:30.6122170Z  --github-branch "$curr_branch" \ 2025-09-07T06:09:30.6122492Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T06:09:30.6122814Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T06:09:30.6123151Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T06:09:30.6123470Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T06:09:30.6123836Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T06:09:30.6124295Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T06:09:30.6124847Z  --pr-number "${PR_NUMBER}" 2025-09-07T06:09:30.6156293Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:30.6156566Z env: 2025-09-07T06:09:30.6157235Z GITHUB_TOKEN: *** 2025-09-07T06:09:30.6157477Z ISSUE_NUMBER: 5132 2025-09-07T06:09:30.6157731Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:30.6158017Z ISSUE_OWNER: 2025-09-07T06:09:30.6158247Z CHECK_EXPERIMENTS: 2025-09-07T06:09:30.6158482Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:30.6158722Z PR_NUMBER: 2025-09-07T06:09:30.6158937Z ##[endgroup] 2025-09-07T06:09:30.6209260Z Current branch is 'main' 2025-09-07T06:09:31.9225628Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T06:09:31.9227853Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T06:09:31.9229359Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T06:09:31.9230536Z INFO : Setting output: label-type='' 2025-09-07T06:09:31.9571923Z Evaluate and set job outputs 2025-09-07T06:09:31.9579287Z Cleaning up orphan processes