2025-09-07T06:09:27.9515227Z Current runner version: '2.328.0' 2025-09-07T06:09:27.9539532Z ##[group]Runner Image Provisioner 2025-09-07T06:09:27.9540299Z Hosted Compute Agent 2025-09-07T06:09:27.9540887Z Version: 20250829.383 2025-09-07T06:09:27.9541507Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T06:09:27.9542364Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T06:09:27.9543076Z ##[endgroup] 2025-09-07T06:09:27.9543593Z ##[group]Operating System 2025-09-07T06:09:27.9544150Z Ubuntu 2025-09-07T06:09:27.9544571Z 24.04.3 2025-09-07T06:09:27.9545108Z LTS 2025-09-07T06:09:27.9545597Z ##[endgroup] 2025-09-07T06:09:27.9546028Z ##[group]Runner Image 2025-09-07T06:09:27.9546619Z Image: ubuntu-24.04 2025-09-07T06:09:27.9547109Z Version: 20250831.1.0 2025-09-07T06:09:27.9548100Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T06:09:27.9549674Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T06:09:27.9550731Z ##[endgroup] 2025-09-07T06:09:27.9551687Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T06:09:27.9554337Z Contents: read 2025-09-07T06:09:27.9555254Z Metadata: read 2025-09-07T06:09:27.9555950Z ##[endgroup] 2025-09-07T06:09:27.9558039Z Secret source: Actions 2025-09-07T06:09:27.9559384Z Prepare workflow directory 2025-09-07T06:09:28.0066902Z Prepare all required actions 2025-09-07T06:09:28.0122797Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T06:09:28.0127744Z ##[group] Inputs 2025-09-07T06:09:28.0128344Z check_experiments: 2025-09-07T06:09:28.0128944Z opt_out_experiments: 2025-09-07T06:09:28.0129535Z triggering_actor: pytorchmergebot 2025-09-07T06:09:28.0130188Z issue_owner: 2025-09-07T06:09:28.0130681Z curr_branch: main 2025-09-07T06:09:28.0131195Z curr_ref_type: branch 2025-09-07T06:09:28.0131849Z issue_number: 5132 2025-09-07T06:09:28.0132698Z ##[endgroup] 2025-09-07T06:09:28.0133518Z Complete job name: before-test / get-label-type / runner-determinator 2025-09-07T06:09:28.5494003Z ##[group]Run cat < runner_determinator.py 2025-09-07T06:09:28.5496431Z cat < runner_determinator.py 2025-09-07T06:09:28.5497107Z # flake8: noqa: G004 2025-09-07T06:09:28.5497755Z  2025-09-07T06:09:28.5498559Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:28.5499715Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:28.5500734Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:28.5501513Z  2025-09-07T06:09:28.5501986Z """ 2025-09-07T06:09:28.5502964Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:28.5504103Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:28.5505303Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:28.5506441Z of which runners should be used to run which job. 2025-09-07T06:09:28.5507116Z  2025-09-07T06:09:28.5507867Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:28.5509071Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:28.5510138Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:28.5511023Z list, defined. 2025-09-07T06:09:28.5511604Z  2025-09-07T06:09:28.5512462Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:28.5513597Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:28.5514725Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:28.5515499Z  2025-09-07T06:09:28.5516499Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:28.5517680Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:28.5518597Z experiments which the user could be opted in to. 2025-09-07T06:09:28.5519321Z  2025-09-07T06:09:28.5519904Z The user list has the following rules: 2025-09-07T06:09:28.5520715Z  2025-09-07T06:09:28.5521443Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:28.5522850Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:28.5523952Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:28.5524704Z  2025-09-07T06:09:28.5525199Z Example config: 2025-09-07T06:09:28.5525858Z  # A list of experiments that can be opted into. 2025-09-07T06:09:28.5526723Z  # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:28.5603148Z  # Expected syntax is: 2025-09-07T06:09:28.5604108Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:28.5605194Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:28.5606018Z  2025-09-07T06:09:28.5606425Z  experiments: 2025-09-07T06:09:28.5606879Z  lf: 2025-09-07T06:09:28.5607317Z  rollout_percent: 25 2025-09-07T06:09:28.5607841Z  all_branches: false 2025-09-07T06:09:28.5608359Z  default: true 2025-09-07T06:09:28.5608825Z  --- 2025-09-07T06:09:28.5609216Z  2025-09-07T06:09:28.5609592Z  # Opt-ins: 2025-09-07T06:09:28.5610266Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:28.5611471Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:28.5612662Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:28.5613422Z  # Experiments should be from the above list. 2025-09-07T06:09:28.5614003Z  2025-09-07T06:09:28.5614406Z  @User1,-lf,split_build 2025-09-07T06:09:28.5614911Z  @User2,lf 2025-09-07T06:09:28.5615371Z  @User3,split_build 2025-09-07T06:09:28.5615846Z """ 2025-09-07T06:09:28.5616222Z  2025-09-07T06:09:28.5616598Z import json 2025-09-07T06:09:28.5617034Z import logging 2025-09-07T06:09:28.5617474Z import os 2025-09-07T06:09:28.5617889Z import random 2025-09-07T06:09:28.5618317Z import re 2025-09-07T06:09:28.5618720Z import sys 2025-09-07T06:09:28.5619191Z from argparse import ArgumentParser 2025-09-07T06:09:28.5619866Z from collections.abc import Iterable 2025-09-07T06:09:28.5620449Z from functools import cache 2025-09-07T06:09:28.5620995Z from logging import LogRecord 2025-09-07T06:09:28.5621549Z from typing import Any, NamedTuple 2025-09-07T06:09:28.5622290Z from urllib.request import Request, urlopen 2025-09-07T06:09:28.5622877Z  2025-09-07T06:09:28.5623252Z import yaml 2025-09-07T06:09:28.5623715Z from github import Auth, Github 2025-09-07T06:09:28.5624268Z from github.Issue import Issue 2025-09-07T06:09:28.5624777Z  2025-09-07T06:09:28.5625128Z  2025-09-07T06:09:28.5625590Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:28.5626357Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:28.5627332Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:28.5628101Z  2025-09-07T06:09:28.5628749Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:28.5629389Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:28.5629990Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:28.5630639Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:28.5631202Z  2025-09-07T06:09:28.5631625Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:28.5632272Z  2025-09-07T06:09:28.5632671Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:28.5633193Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:28.5633683Z  2025-09-07T06:09:28.5634046Z  2025-09-07T06:09:28.5634446Z class Experiment(NamedTuple): 2025-09-07T06:09:28.5635011Z  rollout_perc: float = ( 2025-09-07T06:09:28.5635751Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:28.5636486Z  ) 2025-09-07T06:09:28.5636902Z  all_branches: bool = ( 2025-09-07T06:09:28.5637632Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:28.5638347Z  ) 2025-09-07T06:09:28.5638751Z  default: bool = ( 2025-09-07T06:09:28.5639418Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:28.5640147Z  ) 2025-09-07T06:09:28.5640538Z  2025-09-07T06:09:28.5640941Z  # Add more fields as needed 2025-09-07T06:09:28.5641451Z  2025-09-07T06:09:28.5641806Z  2025-09-07T06:09:28.5642306Z class Settings(NamedTuple): 2025-09-07T06:09:28.5642824Z  """ 2025-09-07T06:09:28.5643359Z  Settings for the experiments that can be opted into. 2025-09-07T06:09:28.5643990Z  """ 2025-09-07T06:09:28.5644373Z  2025-09-07T06:09:28.5644818Z  experiments: dict[str, Experiment] = {} 2025-09-07T06:09:28.5645390Z  2025-09-07T06:09:28.5645886Z  2025-09-07T06:09:28.5646341Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:28.5647058Z  """Color codes the log messages based on the log level""" 2025-09-07T06:09:28.5647703Z  2025-09-07T06:09:28.5648075Z  COLORS = { 2025-09-07T06:09:28.5648548Z  "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:28.5649112Z  "ERROR": "\033[31m", # Red 2025-09-07T06:09:28.5649671Z  "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:28.5650226Z  "INFO": "\033[0m", # Reset 2025-09-07T06:09:28.5650774Z  "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:28.5651295Z  } 2025-09-07T06:09:28.5651677Z  2025-09-07T06:09:28.5652381Z  def format(self, record: LogRecord) -> str: 2025-09-07T06:09:28.5653224Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:28.5654099Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:28.5654738Z  return super().format(record) 2025-09-07T06:09:28.5655276Z  2025-09-07T06:09:28.5655638Z  2025-09-07T06:09:28.5656062Z handler = logging.StreamHandler() 2025-09-07T06:09:28.5656883Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:28.5657650Z  2025-09-07T06:09:28.5658150Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:28.5658803Z log.addHandler(handler) 2025-09-07T06:09:28.5659338Z log.setLevel(logging.INFO) 2025-09-07T06:09:28.5659843Z  2025-09-07T06:09:28.5660212Z  2025-09-07T06:09:28.5660710Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:28.5661336Z  """ 2025-09-07T06:09:28.5661912Z  Defines outputs of the github action that invokes this script 2025-09-07T06:09:28.5662854Z  """ 2025-09-07T06:09:28.5663276Z  if not GITHUB_OUTPUT: 2025-09-07T06:09:28.5664448Z  # 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.5665667Z  log.warning( 2025-09-07T06:09:28.5666631Z  "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.5667625Z  ) 2025-09-07T06:09:28.5668143Z  print(f"::set-output name={key}::{value}") 2025-09-07T06:09:28.5668733Z  return 2025-09-07T06:09:28.5669180Z  2025-09-07T06:09:28.5669610Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:28.5670262Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:28.5670899Z  f.write(f"{key}={value}\n") 2025-09-07T06:09:28.5671434Z  2025-09-07T06:09:28.5671814Z  2025-09-07T06:09:28.5672471Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:28.5673196Z  return frozenset( 2025-09-07T06:09:28.5673897Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:28.5674638Z  ) 2025-09-07T06:09:28.5675033Z  2025-09-07T06:09:28.5675405Z  2025-09-07T06:09:28.5675802Z def parse_args() -> Any: 2025-09-07T06:09:28.5676469Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:28.5677430Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:28.5678262Z  parser.add_argument( 2025-09-07T06:09:28.5678797Z  "--github-issue-repo", 2025-09-07T06:09:28.5679330Z  type=str, 2025-09-07T06:09:28.5679830Z  required=False, 2025-09-07T06:09:28.5680494Z  default="pytorch/test-infra", 2025-09-07T06:09:28.5681118Z  help="GitHub repo to get the issue", 2025-09-07T06:09:28.5681683Z  ) 2025-09-07T06:09:28.5682101Z  parser.add_argument( 2025-09-07T06:09:28.5682726Z  "--github-repo", 2025-09-07T06:09:28.5683228Z  type=str, 2025-09-07T06:09:28.5683711Z  required=True, 2025-09-07T06:09:28.5684265Z  help="GitHub repo where CI is running", 2025-09-07T06:09:28.5684835Z  ) 2025-09-07T06:09:28.5685250Z  parser.add_argument( 2025-09-07T06:09:28.5685952Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:28.5686688Z  ) 2025-09-07T06:09:28.5687100Z  parser.add_argument( 2025-09-07T06:09:28.5687828Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:28.5688574Z  ) 2025-09-07T06:09:28.5688998Z  parser.add_argument( 2025-09-07T06:09:28.5689753Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:28.5690494Z  ) 2025-09-07T06:09:28.5690918Z  parser.add_argument( 2025-09-07T06:09:28.5691676Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:28.5692896Z  ) 2025-09-07T06:09:28.5693343Z  parser.add_argument( 2025-09-07T06:09:28.5693871Z  "--github-ref-type", 2025-09-07T06:09:28.5694395Z  type=str, 2025-09-07T06:09:28.5694867Z  required=True, 2025-09-07T06:09:28.5695560Z  help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:28.5696170Z  ) 2025-09-07T06:09:28.5696587Z  parser.add_argument( 2025-09-07T06:09:28.5697270Z  "--eligible-experiments", 2025-09-07T06:09:28.5697863Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:28.5698441Z  required=False, 2025-09-07T06:09:28.5698942Z  default="", 2025-09-07T06:09:28.5699907Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:28.5700895Z  ) 2025-09-07T06:09:28.5701311Z  parser.add_argument( 2025-09-07T06:09:28.5701848Z  "--opt-out-experiments", 2025-09-07T06:09:28.5702614Z  type=_str_comma_separated_to_set, 2025-09-07T06:09:28.5703193Z  required=False, 2025-09-07T06:09:28.5703690Z  default="", 2025-09-07T06:09:28.5704148Z  help=( 2025-09-07T06:09:28.5704909Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:28.5706144Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:28.5707050Z  ), 2025-09-07T06:09:28.5707458Z  ) 2025-09-07T06:09:28.5707880Z  parser.add_argument( 2025-09-07T06:09:28.5708399Z  "--pr-number", 2025-09-07T06:09:28.5708883Z  type=str, 2025-09-07T06:09:28.5709365Z  required=False, 2025-09-07T06:09:28.5709855Z  default="", 2025-09-07T06:09:28.5710422Z  help="the optional PR number where this is run", 2025-09-07T06:09:28.5711035Z  ) 2025-09-07T06:09:28.5711428Z  2025-09-07T06:09:28.5711838Z  return parser.parse_args() 2025-09-07T06:09:28.5712931Z  2025-09-07T06:09:28.5713344Z  2025-09-07T06:09:28.5714023Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.5715081Z  auth = Auth.Token(github_token) 2025-09-07T06:09:28.5715687Z  return Github(auth=auth) 2025-09-07T06:09:28.5716200Z  2025-09-07T06:09:28.5716566Z  2025-09-07T06:09:28.5717276Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.5718140Z  repo = gh.get_repo(repo) 2025-09-07T06:09:28.5718737Z  return repo.get_issue(number=issue_num) 2025-09-07T06:09:28.5719302Z  2025-09-07T06:09:28.5719672Z  2025-09-07T06:09:28.5720073Z def get_potential_pr_author( 2025-09-07T06:09:28.5720797Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:28.5721538Z ) -> str: 2025-09-07T06:09:28.5722334Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:28.5723266Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:28.5724104Z  # embedded in the tag name: ciflow// 2025-09-07T06:09:28.5724724Z  2025-09-07T06:09:28.5725150Z  gh = get_gh_client(github_token) 2025-09-07T06:09:28.5725679Z  2025-09-07T06:09:28.5726214Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:28.5726910Z  split_tag = ref_name.split("/") 2025-09-07T06:09:28.5727453Z  if ( 2025-09-07T06:09:28.5727904Z  len(split_tag) == 3 2025-09-07T06:09:28.5728458Z  and split_tag[0] == "ciflow" 2025-09-07T06:09:28.5729046Z  and split_tag[2].isnumeric() 2025-09-07T06:09:28.5729582Z  ): 2025-09-07T06:09:28.5730039Z  pr_number = split_tag[2] 2025-09-07T06:09:28.5730585Z  try: 2025-09-07T06:09:28.5731097Z  repository = gh.get_repo(repo) 2025-09-07T06:09:28.5731928Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:28.5732737Z  except Exception as e: 2025-09-07T06:09:28.5733334Z  raise Exception( # noqa: TRY002 2025-09-07T06:09:28.5734065Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:28.5734775Z  ) from e 2025-09-07T06:09:28.5735403Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:28.5736184Z  # In all other cases, return the original input username 2025-09-07T06:09:28.5736830Z  return username 2025-09-07T06:09:28.5737280Z  2025-09-07T06:09:28.5737638Z  2025-09-07T06:09:28.5738097Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:28.5738679Z  """ 2025-09-07T06:09:28.5739409Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:28.5740283Z  """ 2025-09-07T06:09:28.5740897Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:28.5741613Z  2025-09-07T06:09:28.5741980Z  2025-09-07T06:09:28.5742581Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:28.5743127Z  try: 2025-09-07T06:09:28.5743576Z  data = yaml.safe_load(yaml_text) 2025-09-07T06:09:28.5744137Z  return data 2025-09-07T06:09:28.5744616Z  except yaml.YAMLError: 2025-09-07T06:09:28.5745190Z  log.exception("Error loading YAML") 2025-09-07T06:09:28.5745748Z  raise 2025-09-07T06:09:28.5746162Z  2025-09-07T06:09:28.5746525Z  2025-09-07T06:09:28.5747178Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:28.5747977Z  """ 2025-09-07T06:09:28.5748826Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:28.5749663Z  2025-09-07T06:09:28.5750258Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.5751107Z  and the text below is the list of opted in users. 2025-09-07T06:09:28.5751713Z  2025-09-07T06:09:28.5752446Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:28.5753202Z  """ 2025-09-07T06:09:28.5753702Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:28.5754557Z  if len(rollout_state_parts) >= 2: 2025-09-07T06:09:28.5755289Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:28.5755917Z  else: 2025-09-07T06:09:28.5756358Z  return "", rollout_state 2025-09-07T06:09:28.5756870Z  2025-09-07T06:09:28.5757222Z  2025-09-07T06:09:28.5757654Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:28.5758209Z  """ 2025-09-07T06:09:28.5758790Z  Dictionary of users with a list of features they have opted into 2025-09-07T06:09:28.5759475Z  """ 2025-09-07T06:09:28.5759849Z  2025-09-07T06:09:28.5760200Z  2025-09-07T06:09:28.5760773Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:28.5761478Z  """ 2025-09-07T06:09:28.5762368Z  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.5763257Z  2025-09-07T06:09:28.5764133Z  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.5765208Z  - Example line: "@User1,lf,split_build" 2025-09-07T06:09:28.5766103Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:28.5766785Z  2025-09-07T06:09:28.5767136Z  2025-09-07T06:09:28.5767494Z  """ 2025-09-07T06:09:28.5767903Z  optins = UserOptins() 2025-09-07T06:09:28.5768466Z  for user in user_optin_text.split("\n"): 2025-09-07T06:09:28.5769087Z  user = user.strip("\r\n\t -") 2025-09-07T06:09:28.5769703Z  if not user or not user.startswith("@"): 2025-09-07T06:09:28.5770317Z  # Not a valid user. Skip 2025-09-07T06:09:28.5770883Z  continue 2025-09-07T06:09:28.5771330Z  2025-09-07T06:09:28.5771688Z  if user: 2025-09-07T06:09:28.5772295Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:28.5773051Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:28.5773733Z  2025-09-07T06:09:28.5774108Z  return optins 2025-09-07T06:09:28.5774538Z  2025-09-07T06:09:28.5774886Z  2025-09-07T06:09:28.5775414Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:28.5776068Z  """ 2025-09-07T06:09:28.5776512Z  Check if the experiment name is valid. 2025-09-07T06:09:28.5777079Z  A valid name: 2025-09-07T06:09:28.5777806Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:28.5778821Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:28.5779590Z  - Cannot contain spaces 2025-09-07T06:09:28.5780094Z  """ 2025-09-07T06:09:28.5780474Z  2025-09-07T06:09:28.5780960Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:28.5781743Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:28.5782809Z  2025-09-07T06:09:28.5783188Z  if valid: 2025-09-07T06:09:28.5783626Z  return True 2025-09-07T06:09:28.5784064Z  2025-09-07T06:09:28.5784430Z  log.error( 2025-09-07T06:09:28.5785966Z  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.5787596Z  ) 2025-09-07T06:09:28.5787994Z  return False 2025-09-07T06:09:28.5788426Z  2025-09-07T06:09:28.5788773Z  2025-09-07T06:09:28.5789321Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:28.5790005Z  """ 2025-09-07T06:09:28.5790661Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:28.5791432Z  """ 2025-09-07T06:09:28.5791810Z  try: 2025-09-07T06:09:28.5792372Z  if settings_text: 2025-09-07T06:09:28.5793190Z  # 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.5794085Z  # for easy reading 2025-09-07T06:09:28.5794972Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:28.5795935Z  # the backtick character in shell commands. 2025-09-07T06:09:28.5796600Z  backtick = chr(96) # backtick character 2025-09-07T06:09:28.5797340Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:28.5798061Z  settings = load_yaml(settings_text) 2025-09-07T06:09:28.5798614Z  2025-09-07T06:09:28.5799254Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:28.5800191Z  experiments = {} 2025-09-07T06:09:28.5800682Z  2025-09-07T06:09:28.5801297Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:28.5802228Z  if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:28.5803416Z  # 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.5804555Z  continue 2025-09-07T06:09:28.5805054Z  2025-09-07T06:09:28.5805455Z  valid_settings = {} 2025-09-07T06:09:28.5806035Z  for setting in exp_settings: 2025-09-07T06:09:28.5806666Z  if setting not in Experiment._fields: 2025-09-07T06:09:28.5807278Z  log.warning( 2025-09-07T06:09:28.5808080Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:28.5808846Z  ) 2025-09-07T06:09:28.5809334Z  else: 2025-09-07T06:09:28.5809919Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:28.5810540Z  2025-09-07T06:09:28.5811054Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:28.5811753Z  return Settings(experiments) 2025-09-07T06:09:28.5812396Z  2025-09-07T06:09:28.5812781Z  except Exception: 2025-09-07T06:09:28.5813335Z  log.exception("Failed to parse settings") 2025-09-07T06:09:28.5813916Z  2025-09-07T06:09:28.5814298Z  return Settings() 2025-09-07T06:09:28.5814760Z  2025-09-07T06:09:28.5815128Z  2025-09-07T06:09:28.5815740Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:28.5816368Z  """ 2025-09-07T06:09:28.5816867Z  Parse settings, if any, from the rollout state. 2025-09-07T06:09:28.5817695Z  2025-09-07T06:09:28.5818283Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.5819122Z  and the text below is the list of opted in users. 2025-09-07T06:09:28.5819721Z  2025-09-07T06:09:28.5820514Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:28.5821302Z  """ 2025-09-07T06:09:28.5821923Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.5822879Z  return parse_settings_from_text(settings_text) 2025-09-07T06:09:28.5823471Z  2025-09-07T06:09:28.5823829Z  2025-09-07T06:09:28.5824333Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:28.5824946Z  """ 2025-09-07T06:09:28.5825397Z  Parse users from the rollout state. 2025-09-07T06:09:28.5825940Z  2025-09-07T06:09:28.5826300Z  """ 2025-09-07T06:09:28.5826905Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.5827722Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:28.5828310Z  2025-09-07T06:09:28.5828666Z  2025-09-07T06:09:28.5829336Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.5830145Z  """ 2025-09-07T06:09:28.5830625Z  Check if a user is opted into an experiment 2025-09-07T06:09:28.5831202Z  """ 2025-09-07T06:09:28.5831730Z  return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:28.5832457Z  2025-09-07T06:09:28.5833017Z  2025-09-07T06:09:28.5833703Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.5834523Z  """ 2025-09-07T06:09:28.5835050Z  Check if a user explicitly opted out of an experiment 2025-09-07T06:09:28.5835671Z  """ 2025-09-07T06:09:28.5836242Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:28.5837004Z  experiment_optout = "-" + experiment_name 2025-09-07T06:09:28.5837721Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:28.5838376Z  return False 2025-09-07T06:09:28.5838829Z  2025-09-07T06:09:28.5839332Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:28.5840022Z  log.warning( 2025-09-07T06:09:28.5840939Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:28.5841885Z  ) 2025-09-07T06:09:28.5842391Z  2025-09-07T06:09:28.5842766Z  return True 2025-09-07T06:09:28.5843193Z  2025-09-07T06:09:28.5843562Z  2025-09-07T06:09:28.5843947Z def get_runner_prefix( 2025-09-07T06:09:28.5844450Z  rollout_state: str, 2025-09-07T06:09:28.5844992Z  workflow_requestors: Iterable[str], 2025-09-07T06:09:28.5845561Z  branch: str, 2025-09-07T06:09:28.5846141Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.5846891Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.5847525Z  is_canary: bool = False, 2025-09-07T06:09:28.5848027Z ) -> str: 2025-09-07T06:09:28.5848522Z  settings = parse_settings(rollout_state) 2025-09-07T06:09:28.5849165Z  user_optins = parse_users(rollout_state) 2025-09-07T06:09:28.5849728Z  2025-09-07T06:09:28.5850242Z  fleet_prefix = "" 2025-09-07T06:09:28.5850741Z  prefixes = [] 2025-09-07T06:09:28.5851458Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:28.5852587Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:28.5853359Z  log.info( 2025-09-07T06:09:28.5854111Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:28.5854914Z  ) 2025-09-07T06:09:28.5855361Z  continue 2025-09-07T06:09:28.5855809Z  2025-09-07T06:09:28.5856210Z  if opt_out_experiments: 2025-09-07T06:09:28.5856812Z  if experiment_name in opt_out_experiments: 2025-09-07T06:09:28.5857531Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:28.5858174Z  log.info( 2025-09-07T06:09:28.5859196Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:28.5860276Z  ) 2025-09-07T06:09:28.5860734Z  continue 2025-09-07T06:09:28.5861212Z  2025-09-07T06:09:28.5861612Z  if eligible_experiments: 2025-09-07T06:09:28.5862370Z  if experiment_name not in eligible_experiments: 2025-09-07T06:09:28.5863071Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:28.5863674Z  log.info( 2025-09-07T06:09:28.5864545Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:28.5865434Z  ) 2025-09-07T06:09:28.5866033Z  continue 2025-09-07T06:09:28.5866589Z  elif not experiment_settings.default: 2025-09-07T06:09:28.5867166Z  log.info( 2025-09-07T06:09:28.5867907Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:28.5868702Z  ) 2025-09-07T06:09:28.5869136Z  continue 2025-09-07T06:09:28.5869583Z  2025-09-07T06:09:28.5870105Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:28.5870774Z  opted_out_users = [ 2025-09-07T06:09:28.5871292Z  requestor 2025-09-07T06:09:28.5871827Z  for requestor in workflow_requestors 2025-09-07T06:09:28.5872696Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.5873379Z  ] 2025-09-07T06:09:28.5873775Z  2025-09-07T06:09:28.5874170Z  if opted_out_users: 2025-09-07T06:09:28.5874701Z  log.info( 2025-09-07T06:09:28.5875421Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:28.5876171Z  ) 2025-09-07T06:09:28.5876614Z  continue 2025-09-07T06:09:28.5877067Z  2025-09-07T06:09:28.5877580Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:28.5878250Z  opted_in_users = [ 2025-09-07T06:09:28.5878761Z  requestor 2025-09-07T06:09:28.5879288Z  for requestor in workflow_requestors 2025-09-07T06:09:28.5880023Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.5880698Z  ] 2025-09-07T06:09:28.5881097Z  2025-09-07T06:09:28.5881485Z  enabled = False 2025-09-07T06:09:28.5881998Z  if opted_in_users: 2025-09-07T06:09:28.5882762Z  log.info( 2025-09-07T06:09:28.5883477Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:28.5884208Z  ) 2025-09-07T06:09:28.5884656Z  enabled = True 2025-09-07T06:09:28.5885143Z  2025-09-07T06:09:28.5885585Z  elif experiment_settings.rollout_perc: 2025-09-07T06:09:28.5886496Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:28.5887507Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:28.5888209Z  log.info( 2025-09-07T06:09:28.5889160Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:28.5890149Z  ) 2025-09-07T06:09:28.5890625Z  enabled = True 2025-09-07T06:09:28.5891131Z  2025-09-07T06:09:28.5891511Z  if enabled: 2025-09-07T06:09:28.5892003Z  label = experiment_name 2025-09-07T06:09:28.5892734Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:28.5893635Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:28.5894592Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:28.5895429Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:28.5896150Z  if is_canary: 2025-09-07T06:09:28.5896711Z  label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:28.5897309Z  fleet_prefix = label 2025-09-07T06:09:28.5897852Z  else: 2025-09-07T06:09:28.5898491Z  prefixes.append(label) 2025-09-07T06:09:28.5899037Z  2025-09-07T06:09:28.5899430Z  if len(prefixes) > 1: 2025-09-07T06:09:28.5899949Z  log.error( 2025-09-07T06:09:28.5901100Z  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.5902393Z  ) 2025-09-07T06:09:28.5902847Z  prefixes = prefixes[:1] 2025-09-07T06:09:28.5903354Z  2025-09-07T06:09:28.5903752Z  # Fleet always comes first 2025-09-07T06:09:28.5904283Z  if fleet_prefix: 2025-09-07T06:09:28.5904801Z  prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:28.5905350Z  2025-09-07T06:09:28.5905832Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:28.5906447Z  2025-09-07T06:09:28.5906812Z  2025-09-07T06:09:28.5907509Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:28.5908317Z  """ 2025-09-07T06:09:28.5908971Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:28.5909731Z  2025-09-07T06:09:28.5910363Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:28.5911137Z  """ 2025-09-07T06:09:28.5911583Z  gh = get_gh_client(github_token) 2025-09-07T06:09:28.5912301Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:28.5913011Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:28.5913660Z  2025-09-07T06:09:28.5914017Z  2025-09-07T06:09:28.5914657Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:28.5915603Z  for _ in range(num_retries): 2025-09-07T06:09:28.5916133Z  try: 2025-09-07T06:09:28.5916632Z  req = Request(url=url, headers=headers) 2025-09-07T06:09:28.5917400Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:28.5918295Z  return json.loads(content) 2025-09-07T06:09:28.5918888Z  except Exception as e: 2025-09-07T06:09:28.5919501Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:28.5920093Z  2025-09-07T06:09:28.5920707Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:28.5921477Z  return {} 2025-09-07T06:09:28.5921904Z  2025-09-07T06:09:28.5922383Z  2025-09-07T06:09:28.5922750Z @cache 2025-09-07T06:09:28.5923457Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:28.5924293Z  """ 2025-09-07T06:09:28.5924737Z  Dynamically get PR information 2025-09-07T06:09:28.5925272Z  """ 2025-09-07T06:09:28.5925830Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:28.5926517Z  headers = { 2025-09-07T06:09:28.5927048Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:28.5927719Z  "Authorization": f"token {github_token}", 2025-09-07T06:09:28.5928288Z  } 2025-09-07T06:09:28.5928783Z  json_response: dict[str, Any] = download_json( 2025-09-07T06:09:28.5929447Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:28.5930025Z  headers=headers, 2025-09-07T06:09:28.5930511Z  ) 2025-09-07T06:09:28.5930890Z  2025-09-07T06:09:28.5931286Z  if not json_response: 2025-09-07T06:09:28.5931946Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:28.5932896Z  return {} 2025-09-07T06:09:28.5933352Z  2025-09-07T06:09:28.5933740Z  return json_response 2025-09-07T06:09:28.5934227Z  2025-09-07T06:09:28.5934582Z  2025-09-07T06:09:28.5935218Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:28.5935996Z  """ 2025-09-07T06:09:28.5936599Z  Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:28.5937297Z  """ 2025-09-07T06:09:28.5937841Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:28.5938510Z  return { 2025-09-07T06:09:28.5939165Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:28.5939935Z  } 2025-09-07T06:09:28.5940318Z  2025-09-07T06:09:28.5940687Z  2025-09-07T06:09:28.5941070Z def main() -> None: 2025-09-07T06:09:28.5941557Z  args = parse_args() 2025-09-07T06:09:28.5942037Z  2025-09-07T06:09:28.5942590Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:28.5943171Z  2025-09-07T06:09:28.5943563Z  # Check if the PR is opt-out 2025-09-07T06:09:28.5944103Z  if args.pr_number: 2025-09-07T06:09:28.5944840Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:28.5945646Z  if OPT_OUT_LABEL in labels: 2025-09-07T06:09:28.5946189Z  log.info( 2025-09-07T06:09:28.5946954Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:28.5947766Z  ) 2025-09-07T06:09:28.5948398Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.5949133Z  sys.exit() 2025-09-07T06:09:28.5949717Z  2025-09-07T06:09:28.5950094Z  try: 2025-09-07T06:09:28.5950589Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:28.5951364Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:28.5952058Z  ) 2025-09-07T06:09:28.5952565Z  2025-09-07T06:09:28.5952989Z  username = get_potential_pr_author( 2025-09-07T06:09:28.5953569Z  args.github_token, 2025-09-07T06:09:28.5954105Z  args.github_repo, 2025-09-07T06:09:28.5954626Z  args.github_actor, 2025-09-07T06:09:28.5955178Z  args.github_ref_type, 2025-09-07T06:09:28.5955732Z  args.github_branch, 2025-09-07T06:09:28.5956237Z  ) 2025-09-07T06:09:28.5956632Z  2025-09-07T06:09:28.5957153Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:28.5957810Z  2025-09-07T06:09:28.5958252Z  runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:28.5958845Z  rollout_state, 2025-09-07T06:09:28.5959400Z  (args.github_issue_owner, username), 2025-09-07T06:09:28.5959996Z  args.github_branch, 2025-09-07T06:09:28.5960567Z  args.eligible_experiments, 2025-09-07T06:09:28.5961158Z  args.opt_out_experiments, 2025-09-07T06:09:28.5961719Z  is_canary, 2025-09-07T06:09:28.5962285Z  ) 2025-09-07T06:09:28.5962690Z  2025-09-07T06:09:28.5963081Z  except Exception as e: 2025-09-07T06:09:28.5963601Z  log.error( 2025-09-07T06:09:28.5964368Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:28.5965304Z  ) 2025-09-07T06:09:28.5965716Z  2025-09-07T06:09:28.5966290Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.5966997Z  2025-09-07T06:09:28.5967353Z  2025-09-07T06:09:28.5967730Z if __name__ == "__main__": 2025-09-07T06:09:28.5968225Z  main() 2025-09-07T06:09:28.5968633Z  2025-09-07T06:09:28.5968994Z EOF 2025-09-07T06:09:28.5969362Z  2025-09-07T06:09:28.5969766Z cat runner_determinator.py 2025-09-07T06:09:28.6198988Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:28.6199806Z env: 2025-09-07T06:09:28.6200521Z GITHUB_TOKEN: *** 2025-09-07T06:09:28.6200946Z ISSUE_NUMBER: 5132 2025-09-07T06:09:28.6201401Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:28.6201904Z ISSUE_OWNER: 2025-09-07T06:09:28.6202507Z CHECK_EXPERIMENTS: 2025-09-07T06:09:28.6202943Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:28.6203380Z PR_NUMBER: 2025-09-07T06:09:28.6203782Z ##[endgroup] 2025-09-07T06:09:28.6407171Z # flake8: noqa: G004 2025-09-07T06:09:28.6407535Z 2025-09-07T06:09:28.6407998Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T06:09:28.6409011Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T06:09:28.6409856Z # python .github/scripts/update_runner_determinator.py 2025-09-07T06:09:28.6410316Z 2025-09-07T06:09:28.6410501Z """ 2025-09-07T06:09:28.6411117Z This runner determinator is used to determine which set of runners to run a 2025-09-07T06:09:28.6412054Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T06:09:28.6413248Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T06:09:28.6414117Z of which runners should be used to run which job. 2025-09-07T06:09:28.6414533Z 2025-09-07T06:09:28.6414945Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T06:09:28.6416091Z separated by a line containing "---". If the line is not present, the 2025-09-07T06:09:28.6417038Z settings are considered to be empty with only the second part, the user 2025-09-07T06:09:28.6417776Z list, defined. 2025-09-07T06:09:28.6418027Z 2025-09-07T06:09:28.6418424Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T06:09:28.6419416Z used to define any settings that are needed to determine which runners to use. 2025-09-07T06:09:28.6420289Z It's fields are defined by the RolloutSettings class below. 2025-09-07T06:09:28.6420756Z 2025-09-07T06:09:28.6421148Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T06:09:28.6422057Z The user list is also a comma separated list of additional features or 2025-09-07T06:09:28.6423095Z experiments which the user could be opted in to. 2025-09-07T06:09:28.6423518Z 2025-09-07T06:09:28.6423737Z The user list has the following rules: 2025-09-07T06:09:28.6424107Z 2025-09-07T06:09:28.6424456Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T06:09:28.6425351Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T06:09:28.6426155Z - A "#" prefix opts the user out of all experiments 2025-09-07T06:09:28.6426567Z 2025-09-07T06:09:28.6426764Z Example config: 2025-09-07T06:09:28.6427243Z # A list of experiments that can be opted into. 2025-09-07T06:09:28.6427949Z # This defines the behavior they'll induce when opted into. 2025-09-07T06:09:28.6428606Z # Expected syntax is: 2025-09-07T06:09:28.6429285Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T06:09:28.6430306Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T06:09:28.6430947Z 2025-09-07T06:09:28.6431136Z experiments: 2025-09-07T06:09:28.6431561Z lf: 2025-09-07T06:09:28.6431966Z rollout_percent: 25 2025-09-07T06:09:28.6432952Z all_branches: false 2025-09-07T06:09:28.6433451Z default: true 2025-09-07T06:09:28.6433895Z --- 2025-09-07T06:09:28.6434115Z 2025-09-07T06:09:28.6434308Z # Opt-ins: 2025-09-07T06:09:28.6434930Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T06:09:28.6435839Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T06:09:28.6436671Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T06:09:28.6437364Z # Experiments should be from the above list. 2025-09-07T06:09:28.6437759Z 2025-09-07T06:09:28.6437961Z @User1,-lf,split_build 2025-09-07T06:09:28.6438437Z @User2,lf 2025-09-07T06:09:28.6438856Z @User3,split_build 2025-09-07T06:09:28.6439294Z """ 2025-09-07T06:09:28.6439492Z 2025-09-07T06:09:28.6439675Z import json 2025-09-07T06:09:28.6440078Z import logging 2025-09-07T06:09:28.6440484Z import os 2025-09-07T06:09:28.6440888Z import random 2025-09-07T06:09:28.6441299Z import re 2025-09-07T06:09:28.6441696Z import sys 2025-09-07T06:09:28.6442315Z from argparse import ArgumentParser 2025-09-07T06:09:28.6442942Z from collections.abc import Iterable 2025-09-07T06:09:28.6443497Z from functools import cache 2025-09-07T06:09:28.6444003Z from logging import LogRecord 2025-09-07T06:09:28.6444527Z from typing import Any, NamedTuple 2025-09-07T06:09:28.6445104Z from urllib.request import Request, urlopen 2025-09-07T06:09:28.6445499Z 2025-09-07T06:09:28.6445686Z import yaml 2025-09-07T06:09:28.6446104Z from github import Auth, Github 2025-09-07T06:09:28.6446632Z from github.Issue import Issue 2025-09-07T06:09:28.6446952Z 2025-09-07T06:09:28.6446959Z 2025-09-07T06:09:28.6447205Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T06:09:28.6447933Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T06:09:28.6448858Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T06:09:28.6449439Z 2025-09-07T06:09:28.6449689Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T06:09:28.6450446Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T06:09:28.6451001Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T06:09:28.6451594Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T06:09:28.6451957Z 2025-09-07T06:09:28.6452294Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T06:09:28.6452644Z 2025-09-07T06:09:28.6452853Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T06:09:28.6453352Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T06:09:28.6453645Z 2025-09-07T06:09:28.6453652Z 2025-09-07T06:09:28.6453857Z class Experiment(NamedTuple): 2025-09-07T06:09:28.6454377Z rollout_perc: float = ( 2025-09-07T06:09:28.6455064Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T06:09:28.6455785Z ) 2025-09-07T06:09:28.6456186Z all_branches: bool = ( 2025-09-07T06:09:28.6456848Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T06:09:28.6457558Z ) 2025-09-07T06:09:28.6457952Z default: bool = ( 2025-09-07T06:09:28.6458569Z True # If True, the experiment is enabled by default for all queries 2025-09-07T06:09:28.6459242Z ) 2025-09-07T06:09:28.6459451Z 2025-09-07T06:09:28.6459647Z # Add more fields as needed 2025-09-07T06:09:28.6459955Z 2025-09-07T06:09:28.6459962Z 2025-09-07T06:09:28.6460174Z class Settings(NamedTuple): 2025-09-07T06:09:28.6460645Z """ 2025-09-07T06:09:28.6461135Z Settings for the experiments that can be opted into. 2025-09-07T06:09:28.6461736Z """ 2025-09-07T06:09:28.6461942Z 2025-09-07T06:09:28.6462316Z experiments: dict[str, Experiment] = {} 2025-09-07T06:09:28.6462697Z 2025-09-07T06:09:28.6462705Z 2025-09-07T06:09:28.6462936Z class ColorFormatter(logging.Formatter): 2025-09-07T06:09:28.6463602Z """Color codes the log messages based on the log level""" 2025-09-07T06:09:28.6464051Z 2025-09-07T06:09:28.6464240Z COLORS = { 2025-09-07T06:09:28.6464665Z "WARNING": "\033[33m", # Yellow 2025-09-07T06:09:28.6465345Z "ERROR": "\033[31m", # Red 2025-09-07T06:09:28.6465872Z "CRITICAL": "\033[31m", # Red 2025-09-07T06:09:28.6466408Z "INFO": "\033[0m", # Reset 2025-09-07T06:09:28.6466920Z "DEBUG": "\033[0m", # Reset 2025-09-07T06:09:28.6467432Z } 2025-09-07T06:09:28.6467633Z 2025-09-07T06:09:28.6467872Z def format(self, record: LogRecord) -> str: 2025-09-07T06:09:28.6468664Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T06:09:28.6469479Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T06:09:28.6470090Z return super().format(record) 2025-09-07T06:09:28.6470437Z 2025-09-07T06:09:28.6470444Z 2025-09-07T06:09:28.6470664Z handler = logging.StreamHandler() 2025-09-07T06:09:28.6471403Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T06:09:28.6471975Z 2025-09-07T06:09:28.6472348Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T06:09:28.6472969Z log.addHandler(handler) 2025-09-07T06:09:28.6473455Z log.setLevel(logging.INFO) 2025-09-07T06:09:28.6473750Z 2025-09-07T06:09:28.6473757Z 2025-09-07T06:09:28.6474032Z def set_github_output(key: str, value: str) -> None: 2025-09-07T06:09:28.6474627Z """ 2025-09-07T06:09:28.6475160Z Defines outputs of the github action that invokes this script 2025-09-07T06:09:28.6475816Z """ 2025-09-07T06:09:28.6476216Z if not GITHUB_OUTPUT: 2025-09-07T06:09:28.6477332Z # 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.6478498Z log.warning( 2025-09-07T06:09:28.6479385Z "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.6480351Z ) 2025-09-07T06:09:28.6490689Z print(f"::set-output name={key}::{value}") 2025-09-07T06:09:28.6491346Z return 2025-09-07T06:09:28.6491594Z 2025-09-07T06:09:28.6492017Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T06:09:28.6492839Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T06:09:28.6493456Z f.write(f"{key}={value}\n") 2025-09-07T06:09:28.6493795Z 2025-09-07T06:09:28.6493803Z 2025-09-07T06:09:28.6494136Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T06:09:28.6494811Z return frozenset( 2025-09-07T06:09:28.6495462Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T06:09:28.6496194Z ) 2025-09-07T06:09:28.6496404Z 2025-09-07T06:09:28.6496412Z 2025-09-07T06:09:28.6496614Z def parse_args() -> Any: 2025-09-07T06:09:28.6497205Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T06:09:28.6498142Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T06:09:28.6498961Z parser.add_argument( 2025-09-07T06:09:28.6499459Z "--github-issue-repo", 2025-09-07T06:09:28.6499973Z type=str, 2025-09-07T06:09:28.6500411Z required=False, 2025-09-07T06:09:28.6500910Z default="pytorch/test-infra", 2025-09-07T06:09:28.6501496Z help="GitHub repo to get the issue", 2025-09-07T06:09:28.6502047Z ) 2025-09-07T06:09:28.6502637Z parser.add_argument( 2025-09-07T06:09:28.6503123Z "--github-repo", 2025-09-07T06:09:28.6503582Z type=str, 2025-09-07T06:09:28.6504016Z required=True, 2025-09-07T06:09:28.6504524Z help="GitHub repo where CI is running", 2025-09-07T06:09:28.6505097Z ) 2025-09-07T06:09:28.6505507Z parser.add_argument( 2025-09-07T06:09:28.6506153Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T06:09:28.6506856Z ) 2025-09-07T06:09:28.6507255Z parser.add_argument( 2025-09-07T06:09:28.6507919Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T06:09:28.6508649Z ) 2025-09-07T06:09:28.6509052Z parser.add_argument( 2025-09-07T06:09:28.6509894Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T06:09:28.6510638Z ) 2025-09-07T06:09:28.6511050Z parser.add_argument( 2025-09-07T06:09:28.6511737Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T06:09:28.6512739Z ) 2025-09-07T06:09:28.6513145Z parser.add_argument( 2025-09-07T06:09:28.6513640Z "--github-ref-type", 2025-09-07T06:09:28.6514133Z type=str, 2025-09-07T06:09:28.6563667Z required=True, 2025-09-07T06:09:28.6564296Z help="Current GitHub ref type, branch or tag", 2025-09-07T06:09:28.6564946Z ) 2025-09-07T06:09:28.6565352Z parser.add_argument( 2025-09-07T06:09:28.6565858Z "--eligible-experiments", 2025-09-07T06:09:28.6566411Z type=_str_comma_separated_to_set, 2025-09-07T06:09:28.6566983Z required=False, 2025-09-07T06:09:28.6567433Z default="", 2025-09-07T06:09:28.6568363Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T06:09:28.6569347Z ) 2025-09-07T06:09:28.6569749Z parser.add_argument( 2025-09-07T06:09:28.6570249Z "--opt-out-experiments", 2025-09-07T06:09:28.6570789Z type=_str_comma_separated_to_set, 2025-09-07T06:09:28.6571344Z required=False, 2025-09-07T06:09:28.6571789Z default="", 2025-09-07T06:09:28.6572326Z help=( 2025-09-07T06:09:28.6573037Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T06:09:28.6574238Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T06:09:28.6575113Z ), 2025-09-07T06:09:28.6575493Z ) 2025-09-07T06:09:28.6575887Z parser.add_argument( 2025-09-07T06:09:28.6576353Z "--pr-number", 2025-09-07T06:09:28.6576798Z type=str, 2025-09-07T06:09:28.6577218Z required=False, 2025-09-07T06:09:28.6577679Z default="", 2025-09-07T06:09:28.6578357Z help="the optional PR number where this is run", 2025-09-07T06:09:28.6578976Z ) 2025-09-07T06:09:28.6579181Z 2025-09-07T06:09:28.6579398Z return parser.parse_args() 2025-09-07T06:09:28.6579715Z 2025-09-07T06:09:28.6579722Z 2025-09-07T06:09:28.6580159Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.6580972Z auth = Auth.Token(github_token) 2025-09-07T06:09:28.6581514Z return Github(auth=auth) 2025-09-07T06:09:28.6581830Z 2025-09-07T06:09:28.6581837Z 2025-09-07T06:09:28.6582477Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T06:09:28.6583325Z repo = gh.get_repo(repo) 2025-09-07T06:09:28.6583853Z return repo.get_issue(number=issue_num) 2025-09-07T06:09:28.6584228Z 2025-09-07T06:09:28.6584234Z 2025-09-07T06:09:28.6584443Z def get_potential_pr_author( 2025-09-07T06:09:28.6585122Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T06:09:28.6585838Z ) -> str: 2025-09-07T06:09:28.6586387Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T06:09:28.6587235Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T06:09:28.6588009Z # embedded in the tag name: ciflow// 2025-09-07T06:09:28.6588448Z 2025-09-07T06:09:28.6588657Z gh = get_gh_client(github_token) 2025-09-07T06:09:28.6589038Z 2025-09-07T06:09:28.6589334Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T06:09:28.6589991Z split_tag = ref_name.split("/") 2025-09-07T06:09:28.6590520Z if ( 2025-09-07T06:09:28.6590940Z len(split_tag) == 3 2025-09-07T06:09:28.6591460Z and split_tag[0] == "ciflow" 2025-09-07T06:09:28.6592018Z and split_tag[2].isnumeric() 2025-09-07T06:09:28.6592678Z ): 2025-09-07T06:09:28.6593087Z pr_number = split_tag[2] 2025-09-07T06:09:28.6593752Z try: 2025-09-07T06:09:28.6594219Z repository = gh.get_repo(repo) 2025-09-07T06:09:28.6594867Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T06:09:28.6595504Z except Exception as e: 2025-09-07T06:09:28.6596052Z raise Exception( # noqa: TRY002 2025-09-07T06:09:28.6596757Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T06:09:28.6597424Z ) from e 2025-09-07T06:09:28.6597988Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T06:09:28.6598715Z # In all other cases, return the original input username 2025-09-07T06:09:28.6599328Z return username 2025-09-07T06:09:28.6599576Z 2025-09-07T06:09:28.6599583Z 2025-09-07T06:09:28.6599833Z def is_exception_branch(branch: str) -> bool: 2025-09-07T06:09:28.6600391Z """ 2025-09-07T06:09:28.6601065Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T06:09:28.6601890Z """ 2025-09-07T06:09:28.6602579Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T06:09:28.6603124Z 2025-09-07T06:09:28.6603132Z 2025-09-07T06:09:28.6603352Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T06:09:28.6603879Z try: 2025-09-07T06:09:28.6604298Z data = yaml.safe_load(yaml_text) 2025-09-07T06:09:28.6604831Z return data 2025-09-07T06:09:28.6605274Z except yaml.YAMLError: 2025-09-07T06:09:28.6605781Z log.exception("Error loading YAML") 2025-09-07T06:09:28.6606324Z raise 2025-09-07T06:09:28.6606548Z 2025-09-07T06:09:28.6606555Z 2025-09-07T06:09:28.6606986Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T06:09:28.6607760Z """ 2025-09-07T06:09:28.6608405Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T06:09:28.6609017Z 2025-09-07T06:09:28.6609512Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.6610320Z and the text below is the list of opted in users. 2025-09-07T06:09:28.6610735Z 2025-09-07T06:09:28.6611136Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T06:09:28.6611877Z """ 2025-09-07T06:09:28.6612451Z rollout_state_parts = rollout_state.split("---") 2025-09-07T06:09:28.6613105Z if len(rollout_state_parts) >= 2: 2025-09-07T06:09:28.6613751Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T06:09:28.6614364Z else: 2025-09-07T06:09:28.6614772Z return "", rollout_state 2025-09-07T06:09:28.6615089Z 2025-09-07T06:09:28.6615097Z 2025-09-07T06:09:28.6615317Z class UserOptins(dict[str, list[str]]): 2025-09-07T06:09:28.6615856Z """ 2025-09-07T06:09:28.6616403Z Dictionary of users with a list of features they have opted into 2025-09-07T06:09:28.6617095Z """ 2025-09-07T06:09:28.6617302Z 2025-09-07T06:09:28.6617309Z 2025-09-07T06:09:28.6617681Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T06:09:28.6618364Z """ 2025-09-07T06:09:28.6619117Z 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.6619829Z 2025-09-07T06:09:28.6620474Z 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.6621521Z - Example line: "@User1,lf,split_build" 2025-09-07T06:09:28.6622377Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T06:09:28.6622891Z 2025-09-07T06:09:28.6622898Z 2025-09-07T06:09:28.6623075Z """ 2025-09-07T06:09:28.6623475Z optins = UserOptins() 2025-09-07T06:09:28.6623990Z for user in user_optin_text.split("\n"): 2025-09-07T06:09:28.6624585Z user = user.strip("\r\n\t -") 2025-09-07T06:09:28.6625159Z if not user or not user.startswith("@"): 2025-09-07T06:09:28.6625897Z # Not a valid user. Skip 2025-09-07T06:09:28.6626407Z continue 2025-09-07T06:09:28.6626668Z 2025-09-07T06:09:28.6626848Z if user: 2025-09-07T06:09:28.6627311Z usr_name = user.split(",")[0].strip("@") 2025-09-07T06:09:28.6628042Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T06:09:28.6628549Z 2025-09-07T06:09:28.6628741Z return optins 2025-09-07T06:09:28.6628987Z 2025-09-07T06:09:28.6628995Z 2025-09-07T06:09:28.6629307Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T06:09:28.6629939Z """ 2025-09-07T06:09:28.6630361Z Check if the experiment name is valid. 2025-09-07T06:09:28.6630917Z A valid name: 2025-09-07T06:09:28.6631582Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T06:09:28.6632693Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T06:09:28.6633472Z - Cannot contain spaces 2025-09-07T06:09:28.6633958Z """ 2025-09-07T06:09:28.6634173Z 2025-09-07T06:09:28.6634455Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T06:09:28.6635177Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T06:09:28.6635644Z 2025-09-07T06:09:28.6635821Z if valid: 2025-09-07T06:09:28.6636221Z return True 2025-09-07T06:09:28.6636476Z 2025-09-07T06:09:28.6636657Z log.error( 2025-09-07T06:09:28.6638186Z 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.6639842Z ) 2025-09-07T06:09:28.6640218Z return False 2025-09-07T06:09:28.6640464Z 2025-09-07T06:09:28.6640470Z 2025-09-07T06:09:28.6640804Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T06:09:28.6641459Z """ 2025-09-07T06:09:28.6642309Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T06:09:28.6643107Z """ 2025-09-07T06:09:28.6643490Z try: 2025-09-07T06:09:28.6643887Z if settings_text: 2025-09-07T06:09:28.6644660Z # 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.6645501Z # for easy reading 2025-09-07T06:09:28.6646314Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T06:09:28.6647237Z # the backtick character in shell commands. 2025-09-07T06:09:28.6647869Z backtick = chr(96) # backtick character 2025-09-07T06:09:28.6648570Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T06:09:28.6649256Z settings = load_yaml(settings_text) 2025-09-07T06:09:28.6649647Z 2025-09-07T06:09:28.6650091Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T06:09:28.6650896Z experiments = {} 2025-09-07T06:09:28.6651203Z 2025-09-07T06:09:28.6651604Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T06:09:28.6652505Z if not is_valid_experiment_name(exp_name): 2025-09-07T06:09:28.6653661Z # 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.6654756Z continue 2025-09-07T06:09:28.6655047Z 2025-09-07T06:09:28.6655256Z valid_settings = {} 2025-09-07T06:09:28.6655802Z for setting in exp_settings: 2025-09-07T06:09:28.6656400Z if setting not in Experiment._fields: 2025-09-07T06:09:28.6656982Z log.warning( 2025-09-07T06:09:28.6657723Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T06:09:28.6658622Z ) 2025-09-07T06:09:28.6659078Z else: 2025-09-07T06:09:28.6659623Z valid_settings[setting] = exp_settings[setting] 2025-09-07T06:09:28.6660065Z 2025-09-07T06:09:28.6660360Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T06:09:28.6661017Z return Settings(experiments) 2025-09-07T06:09:28.6661381Z 2025-09-07T06:09:28.6661572Z except Exception: 2025-09-07T06:09:28.6662090Z log.exception("Failed to parse settings") 2025-09-07T06:09:28.6662646Z 2025-09-07T06:09:28.6662844Z return Settings() 2025-09-07T06:09:28.6663122Z 2025-09-07T06:09:28.6663129Z 2025-09-07T06:09:28.6663403Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T06:09:28.6664006Z """ 2025-09-07T06:09:28.6664460Z Parse settings, if any, from the rollout state. 2025-09-07T06:09:28.6664883Z 2025-09-07T06:09:28.6665264Z If the issue body contains "---" then the text above that is the settings 2025-09-07T06:09:28.6666077Z and the text below is the list of opted in users. 2025-09-07T06:09:28.6666503Z 2025-09-07T06:09:28.6666940Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T06:09:28.6667734Z """ 2025-09-07T06:09:28.6668318Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.6669109Z return parse_settings_from_text(settings_text) 2025-09-07T06:09:28.6669525Z 2025-09-07T06:09:28.6669532Z 2025-09-07T06:09:28.6669794Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T06:09:28.6670382Z """ 2025-09-07T06:09:28.6670794Z Parse users from the rollout state. 2025-09-07T06:09:28.6671168Z 2025-09-07T06:09:28.6671341Z """ 2025-09-07T06:09:28.6671896Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T06:09:28.6672785Z return parse_user_opt_in_from_text(users_text) 2025-09-07T06:09:28.6673206Z 2025-09-07T06:09:28.6673213Z 2025-09-07T06:09:28.6673784Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.6674578Z """ 2025-09-07T06:09:28.6675021Z Check if a user is opted into an experiment 2025-09-07T06:09:28.6675584Z """ 2025-09-07T06:09:28.6676065Z return experiment_name in user_optins.get(user, []) 2025-09-07T06:09:28.6676497Z 2025-09-07T06:09:28.6676504Z 2025-09-07T06:09:28.6676953Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T06:09:28.6677761Z """ 2025-09-07T06:09:28.6678251Z Check if a user explicitly opted out of an experiment 2025-09-07T06:09:28.6678852Z """ 2025-09-07T06:09:28.6679371Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T06:09:28.6680085Z experiment_optout = "-" + experiment_name 2025-09-07T06:09:28.6680753Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T06:09:28.6681391Z return False 2025-09-07T06:09:28.6681667Z 2025-09-07T06:09:28.6681961Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T06:09:28.6682716Z log.warning( 2025-09-07T06:09:28.6683556Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T06:09:28.6684493Z ) 2025-09-07T06:09:28.6684705Z 2025-09-07T06:09:28.6684890Z return True 2025-09-07T06:09:28.6685143Z 2025-09-07T06:09:28.6685149Z 2025-09-07T06:09:28.6685345Z def get_runner_prefix( 2025-09-07T06:09:28.6685812Z rollout_state: str, 2025-09-07T06:09:28.6686307Z workflow_requestors: Iterable[str], 2025-09-07T06:09:28.6686854Z branch: str, 2025-09-07T06:09:28.6687394Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.6688093Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T06:09:28.6688706Z is_canary: bool = False, 2025-09-07T06:09:28.6689240Z ) -> str: 2025-09-07T06:09:28.6689825Z settings = parse_settings(rollout_state) 2025-09-07T06:09:28.6690447Z user_optins = parse_users(rollout_state) 2025-09-07T06:09:28.6690827Z 2025-09-07T06:09:28.6691022Z fleet_prefix = "" 2025-09-07T06:09:28.6691484Z prefixes = [] 2025-09-07T06:09:28.6692251Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T06:09:28.6693231Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T06:09:28.6693988Z log.info( 2025-09-07T06:09:28.6694696Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T06:09:28.6695501Z ) 2025-09-07T06:09:28.6695912Z continue 2025-09-07T06:09:28.6696179Z 2025-09-07T06:09:28.6696390Z if opt_out_experiments: 2025-09-07T06:09:28.6696951Z if experiment_name in opt_out_experiments: 2025-09-07T06:09:28.6697630Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T06:09:28.6698258Z log.info( 2025-09-07T06:09:28.6699232Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T06:09:28.6700251Z ) 2025-09-07T06:09:28.6700679Z continue 2025-09-07T06:09:28.6700959Z 2025-09-07T06:09:28.6701166Z if eligible_experiments: 2025-09-07T06:09:28.6701742Z if experiment_name not in eligible_experiments: 2025-09-07T06:09:28.6702559Z exp_list = ", ".join(eligible_experiments) 2025-09-07T06:09:28.6703158Z log.info( 2025-09-07T06:09:28.6703965Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T06:09:28.6704838Z ) 2025-09-07T06:09:28.6705266Z continue 2025-09-07T06:09:28.6705770Z elif not experiment_settings.default: 2025-09-07T06:09:28.6706335Z log.info( 2025-09-07T06:09:28.6707150Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T06:09:28.6707942Z ) 2025-09-07T06:09:28.6708350Z continue 2025-09-07T06:09:28.6708359Z 2025-09-07T06:09:28.6708659Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T06:09:28.6708857Z opted_out_users = [ 2025-09-07T06:09:28.6709040Z requestor 2025-09-07T06:09:28.6709269Z for requestor in workflow_requestors 2025-09-07T06:09:28.6709596Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.6709781Z ] 2025-09-07T06:09:28.6709788Z 2025-09-07T06:09:28.6709987Z if opted_out_users: 2025-09-07T06:09:28.6710173Z log.info( 2025-09-07T06:09:28.6710564Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T06:09:28.6710747Z ) 2025-09-07T06:09:28.6710936Z continue 2025-09-07T06:09:28.6710949Z 2025-09-07T06:09:28.6711245Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T06:09:28.6711444Z opted_in_users = [ 2025-09-07T06:09:28.6711627Z requestor 2025-09-07T06:09:28.6711859Z for requestor in workflow_requestors 2025-09-07T06:09:28.6712282Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T06:09:28.6712465Z ] 2025-09-07T06:09:28.6712473Z 2025-09-07T06:09:28.6712666Z enabled = False 2025-09-07T06:09:28.6712866Z if opted_in_users: 2025-09-07T06:09:28.6713050Z log.info( 2025-09-07T06:09:28.6713428Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T06:09:28.6713613Z ) 2025-09-07T06:09:28.6713803Z enabled = True 2025-09-07T06:09:28.6713811Z 2025-09-07T06:09:28.6714048Z elif experiment_settings.rollout_perc: 2025-09-07T06:09:28.6714531Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T06:09:28.6714992Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T06:09:28.6715179Z log.info( 2025-09-07T06:09:28.6715789Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T06:09:28.6715977Z ) 2025-09-07T06:09:28.6716171Z enabled = True 2025-09-07T06:09:28.6716180Z 2025-09-07T06:09:28.6716360Z if enabled: 2025-09-07T06:09:28.6716572Z label = experiment_name 2025-09-07T06:09:28.6716816Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T06:09:28.6717277Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T06:09:28.6717565Z # - If it's enabled, then we always list it's prefix first 2025-09-07T06:09:28.6717900Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T06:09:28.6718095Z if is_canary: 2025-09-07T06:09:28.6718318Z label += CANARY_FLEET_SUFFIX 2025-09-07T06:09:28.6718523Z fleet_prefix = label 2025-09-07T06:09:28.6718703Z else: 2025-09-07T06:09:28.6718919Z prefixes.append(label) 2025-09-07T06:09:28.6718926Z 2025-09-07T06:09:28.6719123Z if len(prefixes) > 1: 2025-09-07T06:09:28.6719314Z log.error( 2025-09-07T06:09:28.6720177Z 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.6720356Z ) 2025-09-07T06:09:28.6720566Z prefixes = prefixes[:1] 2025-09-07T06:09:28.6720574Z 2025-09-07T06:09:28.6720780Z # Fleet always comes first 2025-09-07T06:09:28.6720972Z if fleet_prefix: 2025-09-07T06:09:28.6721208Z prefixes.insert(0, fleet_prefix) 2025-09-07T06:09:28.6721221Z 2025-09-07T06:09:28.6721615Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T06:09:28.6721624Z 2025-09-07T06:09:28.6721631Z 2025-09-07T06:09:28.6722106Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T06:09:28.6722398Z """ 2025-09-07T06:09:28.6722815Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T06:09:28.6722823Z 2025-09-07T06:09:28.6723230Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T06:09:28.6723413Z """ 2025-09-07T06:09:28.6723623Z gh = get_gh_client(github_token) 2025-09-07T06:09:28.6723849Z issue = get_issue(gh, repo, issue_num) 2025-09-07T06:09:28.6724147Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T06:09:28.6724154Z 2025-09-07T06:09:28.6724161Z 2025-09-07T06:09:28.6724578Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T06:09:28.6724790Z for _ in range(num_retries): 2025-09-07T06:09:28.6724979Z try: 2025-09-07T06:09:28.6725210Z req = Request(url=url, headers=headers) 2025-09-07T06:09:28.6725513Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T06:09:28.6725723Z return json.loads(content) 2025-09-07T06:09:28.6725935Z except Exception as e: 2025-09-07T06:09:28.6726190Z log.warning(f"Could not download {url}: {e}") 2025-09-07T06:09:28.6726198Z 2025-09-07T06:09:28.6726600Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T06:09:28.6726787Z return {} 2025-09-07T06:09:28.6726794Z 2025-09-07T06:09:28.6726802Z 2025-09-07T06:09:28.6726976Z @cache 2025-09-07T06:09:28.6727428Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T06:09:28.6727611Z """ 2025-09-07T06:09:28.6727823Z Dynamically get PR information 2025-09-07T06:09:28.6727997Z """ 2025-09-07T06:09:28.6728445Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T06:09:28.6728638Z headers = { 2025-09-07T06:09:28.6728883Z "Accept": "application/vnd.github.v3+json", 2025-09-07T06:09:28.6729115Z "Authorization": f"token {github_token}", 2025-09-07T06:09:28.6729296Z } 2025-09-07T06:09:28.6729546Z json_response: dict[str, Any] = download_json( 2025-09-07T06:09:28.6729777Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T06:09:28.6729969Z headers=headers, 2025-09-07T06:09:28.6730153Z ) 2025-09-07T06:09:28.6730162Z 2025-09-07T06:09:28.6730367Z if not json_response: 2025-09-07T06:09:28.6730672Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T06:09:28.6730857Z return {} 2025-09-07T06:09:28.6730866Z 2025-09-07T06:09:28.6731067Z return json_response 2025-09-07T06:09:28.6731074Z 2025-09-07T06:09:28.6731082Z 2025-09-07T06:09:28.6731501Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T06:09:28.6731690Z """ 2025-09-07T06:09:28.6732029Z Dynamically get the latest list of labels from the pull request 2025-09-07T06:09:28.6732317Z """ 2025-09-07T06:09:28.6732621Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T06:09:28.6732806Z return { 2025-09-07T06:09:28.6733187Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T06:09:28.6733363Z } 2025-09-07T06:09:28.6733372Z 2025-09-07T06:09:28.6733384Z 2025-09-07T06:09:28.6733580Z def main() -> None: 2025-09-07T06:09:28.6733777Z args = parse_args() 2025-09-07T06:09:28.6733785Z 2025-09-07T06:09:28.6734024Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T06:09:28.6734032Z 2025-09-07T06:09:28.6734249Z # Check if the PR is opt-out 2025-09-07T06:09:28.6734440Z if args.pr_number: 2025-09-07T06:09:28.6734853Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T06:09:28.6735194Z if OPT_OUT_LABEL in labels: 2025-09-07T06:09:28.6735385Z log.info( 2025-09-07T06:09:28.6735845Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T06:09:28.6736027Z ) 2025-09-07T06:09:28.6736378Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.6736567Z sys.exit() 2025-09-07T06:09:28.6736576Z 2025-09-07T06:09:28.6736750Z try: 2025-09-07T06:09:28.6737004Z rollout_state = get_rollout_state_from_issue( 2025-09-07T06:09:28.6737330Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T06:09:28.6737514Z ) 2025-09-07T06:09:28.6737521Z 2025-09-07T06:09:28.6737749Z username = get_potential_pr_author( 2025-09-07T06:09:28.6737955Z args.github_token, 2025-09-07T06:09:28.6738159Z args.github_repo, 2025-09-07T06:09:28.6738359Z args.github_actor, 2025-09-07T06:09:28.6738568Z args.github_ref_type, 2025-09-07T06:09:28.6738776Z args.github_branch, 2025-09-07T06:09:28.6738955Z ) 2025-09-07T06:09:28.6738964Z 2025-09-07T06:09:28.6739272Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T06:09:28.6739280Z 2025-09-07T06:09:28.6739517Z runner_label_prefix = get_runner_prefix( 2025-09-07T06:09:28.6739714Z rollout_state, 2025-09-07T06:09:28.6739947Z (args.github_issue_owner, username), 2025-09-07T06:09:28.6740148Z args.github_branch, 2025-09-07T06:09:28.6740364Z args.eligible_experiments, 2025-09-07T06:09:28.6740578Z args.opt_out_experiments, 2025-09-07T06:09:28.6740772Z is_canary, 2025-09-07T06:09:28.6740946Z ) 2025-09-07T06:09:28.6740955Z 2025-09-07T06:09:28.6741160Z except Exception as e: 2025-09-07T06:09:28.6741351Z log.error( 2025-09-07T06:09:28.6741813Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T06:09:28.6742259Z ) 2025-09-07T06:09:28.6742270Z 2025-09-07T06:09:28.6742638Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T06:09:28.6742647Z 2025-09-07T06:09:28.6742654Z 2025-09-07T06:09:28.6742852Z if __name__ == "__main__": 2025-09-07T06:09:28.6743039Z main() 2025-09-07T06:09:28.6743046Z 2025-09-07T06:09:28.6836658Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:28.6837555Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T06:09:28.6867024Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:28.6867497Z env: 2025-09-07T06:09:28.6868098Z GITHUB_TOKEN: *** 2025-09-07T06:09:28.6868501Z ISSUE_NUMBER: 5132 2025-09-07T06:09:28.6868939Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:28.6869432Z ISSUE_OWNER: 2025-09-07T06:09:28.6869827Z CHECK_EXPERIMENTS: 2025-09-07T06:09:28.6870273Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:28.6870702Z PR_NUMBER: 2025-09-07T06:09:28.6871071Z ##[endgroup] 2025-09-07T06:09:29.6236889Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T06:09:30.2060261Z Collecting urllib3==1.26.18 2025-09-07T06:09:30.2507670Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T06:09:30.2734069Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.4 MB/s eta 0:00:00 2025-09-07T06:09:30.2994376Z Collecting PyGithub==2.3.0 2025-09-07T06:09:30.3069736Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T06:09:30.3530155Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T06:09:30.3604603Z 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:30.3649336Z 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:30.3661092Z 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:30.3679754Z 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:30.3965090Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T06:09:30.4034979Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T06:09:30.4264086Z 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:30.5485413Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:30.5556343Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T06:09:30.6781779Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T06:09:30.6851797Z 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:30.7083952Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T06:09:30.7151947Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T06:09:30.7461357Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T06:09:30.7602550Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 10.9 MB/s eta 0:00:00 2025-09-07T06:09:30.7689352Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T06:09:30.7953691Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 13.9 MB/s eta 0:00:00 2025-09-07T06:09:30.8028045Z 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:30.8505775Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 18.3 MB/s eta 0:00:00 2025-09-07T06:09:30.8580444Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T06:09:30.8683325Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T06:09:30.8895922Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 23.8 MB/s eta 0:00:00 2025-09-07T06:09:30.8966982Z 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:30.9016565Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 23.1 MB/s eta 0:00:00 2025-09-07T06:09:30.9089274Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T06:09:30.9149603Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 24.0 MB/s eta 0:00:00 2025-09-07T06:09:31.2110150Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T06:09:31.7419084Z 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:31.8169208Z ##[group]Run curr_branch="main" 2025-09-07T06:09:31.8169515Z curr_branch="main" 2025-09-07T06:09:31.8169734Z curr_ref_type="branch" 2025-09-07T06:09:31.8170007Z echo "Current branch is '$curr_branch'" 2025-09-07T06:09:31.8170255Z  2025-09-07T06:09:31.8170438Z python3 runner_determinator.py \ 2025-09-07T06:09:31.8170709Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T06:09:31.8170972Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T06:09:31.8171236Z  --github-branch "$curr_branch" \ 2025-09-07T06:09:31.8171495Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T06:09:31.8171773Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T06:09:31.8172040Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T06:09:31.8172534Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T06:09:31.8172826Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T06:09:31.8173186Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T06:09:31.8173469Z  --pr-number "${PR_NUMBER}" 2025-09-07T06:09:31.8204477Z shell: /usr/bin/bash -e {0} 2025-09-07T06:09:31.8204689Z env: 2025-09-07T06:09:31.8205223Z GITHUB_TOKEN: *** 2025-09-07T06:09:31.8205405Z ISSUE_NUMBER: 5132 2025-09-07T06:09:31.8205609Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T06:09:31.8205834Z ISSUE_OWNER: 2025-09-07T06:09:31.8206008Z CHECK_EXPERIMENTS: 2025-09-07T06:09:31.8206190Z OPT_OUT_EXPERIMENTS: 2025-09-07T06:09:31.8206372Z PR_NUMBER: 2025-09-07T06:09:31.8206534Z ##[endgroup] 2025-09-07T06:09:31.8254189Z Current branch is 'main' 2025-09-07T06:09:33.3253517Z INFO : Based on rollout percentage of 60%, enabling experiment lf. 2025-09-07T06:09:33.3254867Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T06:09:33.3255797Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T06:09:33.3256517Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T06:09:33.3257053Z INFO : Setting output: label-type='lf.' 2025-09-07T06:09:33.3562835Z Evaluate and set job outputs 2025-09-07T06:09:33.3569460Z Set output 'label-type' 2025-09-07T06:09:33.3571487Z Cleaning up orphan processes