2025-06-01T20:56:26.8611384Z Current runner version: '2.324.0' 2025-06-01T20:56:26.8636444Z ##[group]Operating System 2025-06-01T20:56:26.8637273Z Ubuntu 2025-06-01T20:56:26.8637747Z 24.04.2 2025-06-01T20:56:26.8638213Z LTS 2025-06-01T20:56:26.8638667Z ##[endgroup] 2025-06-01T20:56:26.8639255Z ##[group]Runner Image 2025-06-01T20:56:26.8639794Z Image: ubuntu-24.04 2025-06-01T20:56:26.8640300Z Version: 20250527.1.0 2025-06-01T20:56:26.8641551Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250527.1/images/ubuntu/Ubuntu2404-Readme.md 2025-06-01T20:56:26.8642978Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250527.1 2025-06-01T20:56:26.8643905Z ##[endgroup] 2025-06-01T20:56:26.8644441Z ##[group]Runner Image Provisioner 2025-06-01T20:56:26.8645058Z 2.0.437.1 2025-06-01T20:56:26.8645486Z ##[endgroup] 2025-06-01T20:56:26.8646494Z ##[group]GITHUB_TOKEN Permissions 2025-06-01T20:56:26.8648587Z Contents: read 2025-06-01T20:56:26.8649247Z Metadata: read 2025-06-01T20:56:26.8649913Z ##[endgroup] 2025-06-01T20:56:26.8652663Z Secret source: Actions 2025-06-01T20:56:26.8653593Z Prepare workflow directory 2025-06-01T20:56:26.9163264Z Prepare all required actions 2025-06-01T20:56:26.9218122Z Complete job name: before-test / get-label-type / runner-determinator 2025-06-01T20:56:27.4521734Z ##[group]Run cat < runner_determinator.py 2025-06-01T20:56:27.4524300Z cat < runner_determinator.py 2025-06-01T20:56:27.4525037Z # flake8: noqa: G004 2025-06-01T20:56:27.4525631Z  2025-06-01T20:56:27.4526392Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:27.4527583Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:27.4528632Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:27.4529352Z  2025-06-01T20:56:27.4529853Z """ 2025-06-01T20:56:27.4530592Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:27.4531899Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:27.4533199Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:27.4534217Z of which runners should be used to run which job. 2025-06-01T20:56:27.4534944Z  2025-06-01T20:56:27.4535757Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:27.4536893Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:27.4537955Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:27.4538919Z list, defined. 2025-06-01T20:56:27.4539422Z  2025-06-01T20:56:27.4540139Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:27.4541667Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:27.4542744Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:27.4543524Z  2025-06-01T20:56:27.4544291Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:27.4545430Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:27.4546409Z experiments which the user could be opted in to. 2025-06-01T20:56:27.4547136Z  2025-06-01T20:56:27.4547688Z The user list has the following rules: 2025-06-01T20:56:27.4548310Z  2025-06-01T20:56:27.4549076Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:27.4550170Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:27.4551289Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:27.4552331Z  2025-06-01T20:56:27.4552913Z Example config: 2025-06-01T20:56:27.4553572Z  # A list of experiments that can be opted into. 2025-06-01T20:56:27.4554521Z  # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:27.4555319Z  # Expected syntax is: 2025-06-01T20:56:27.4556176Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:27.4557321Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:27.4558326Z  2025-06-01T20:56:27.4558838Z  experiments: 2025-06-01T20:56:27.4559334Z  lf: 2025-06-01T20:56:27.4559922Z  rollout_percent: 25 2025-06-01T20:56:27.4560510Z  all_branches: false 2025-06-01T20:56:27.4561333Z  default: true 2025-06-01T20:56:27.4561950Z  --- 2025-06-01T20:56:27.4562466Z  2025-06-01T20:56:27.4562936Z  # Opt-ins: 2025-06-01T20:56:27.4563774Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:27.4565026Z  # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:27.4565998Z  # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:27.4566904Z  # Experiments should be from the above list. 2025-06-01T20:56:27.4567590Z  2025-06-01T20:56:27.4568075Z  @User1,-lf,split_build 2025-06-01T20:56:27.4568734Z  @User2,lf 2025-06-01T20:56:27.4569251Z  @User3,split_build 2025-06-01T20:56:27.4569825Z """ 2025-06-01T20:56:27.4570293Z  2025-06-01T20:56:27.4570814Z import json 2025-06-01T20:56:27.4571448Z import logging 2025-06-01T20:56:27.4652052Z import os 2025-06-01T20:56:27.4652627Z import random 2025-06-01T20:56:27.4653148Z import re 2025-06-01T20:56:27.4653576Z import sys 2025-06-01T20:56:27.4654062Z from argparse import ArgumentParser 2025-06-01T20:56:27.4654694Z from collections.abc import Iterable 2025-06-01T20:56:27.4655306Z from functools import cache 2025-06-01T20:56:27.4655882Z from logging import LogRecord 2025-06-01T20:56:27.4656468Z from typing import Any, NamedTuple 2025-06-01T20:56:27.4657105Z from urllib.request import Request, urlopen 2025-06-01T20:56:27.4657708Z  2025-06-01T20:56:27.4658088Z import yaml 2025-06-01T20:56:27.4658553Z from github import Auth, Github 2025-06-01T20:56:27.4659137Z from github.Issue import Issue 2025-06-01T20:56:27.4659662Z  2025-06-01T20:56:27.4660027Z  2025-06-01T20:56:27.4660505Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:27.4661443Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:27.4662460Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:27.4663249Z  2025-06-01T20:56:27.4663725Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:27.4664379Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:27.4664987Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:27.4665658Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:27.4666247Z  2025-06-01T20:56:27.4666675Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:27.4667227Z  2025-06-01T20:56:27.4667618Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:27.4668168Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:27.4668667Z  2025-06-01T20:56:27.4669026Z  2025-06-01T20:56:27.4669436Z class Experiment(NamedTuple): 2025-06-01T20:56:27.4670010Z  rollout_perc: float = ( 2025-06-01T20:56:27.4671123Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:27.4671888Z  ) 2025-06-01T20:56:27.4672317Z  all_branches: bool = ( 2025-06-01T20:56:27.4673068Z  False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:27.4673900Z  ) 2025-06-01T20:56:27.4674319Z  default: bool = ( 2025-06-01T20:56:27.4675001Z  True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:27.4675731Z  ) 2025-06-01T20:56:27.4676114Z  2025-06-01T20:56:27.4676524Z  # Add more fields as needed 2025-06-01T20:56:27.4677055Z  2025-06-01T20:56:27.4677424Z  2025-06-01T20:56:27.4677827Z class Settings(NamedTuple): 2025-06-01T20:56:27.4678345Z  """ 2025-06-01T20:56:27.4678896Z  Settings for the experiments that can be opted into. 2025-06-01T20:56:27.4679557Z  """ 2025-06-01T20:56:27.4679945Z  2025-06-01T20:56:27.4680379Z  experiments: dict[str, Experiment] = {} 2025-06-01T20:56:27.4681043Z  2025-06-01T20:56:27.4681543Z  2025-06-01T20:56:27.4682012Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:27.4682741Z  """Color codes the log messages based on the log level""" 2025-06-01T20:56:27.4683391Z  2025-06-01T20:56:27.4683772Z  COLORS = { 2025-06-01T20:56:27.4684255Z  "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:27.4684838Z  "ERROR": "\033[31m", # Red 2025-06-01T20:56:27.4685406Z  "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:27.4685981Z  "INFO": "\033[0m", # Reset 2025-06-01T20:56:27.4686554Z  "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:27.4687083Z  } 2025-06-01T20:56:27.4687464Z  2025-06-01T20:56:27.4687920Z  def format(self, record: LogRecord) -> str: 2025-06-01T20:56:27.4688775Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:27.4689653Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:27.4690304Z  return super().format(record) 2025-06-01T20:56:27.4691131Z  2025-06-01T20:56:27.4691505Z  2025-06-01T20:56:27.4691934Z handler = logging.StreamHandler() 2025-06-01T20:56:27.4692764Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:27.4693571Z  2025-06-01T20:56:27.4694069Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:27.4694733Z log.addHandler(handler) 2025-06-01T20:56:27.4695255Z log.setLevel(logging.INFO) 2025-06-01T20:56:27.4695787Z  2025-06-01T20:56:27.4696144Z  2025-06-01T20:56:27.4696646Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:27.4697294Z  """ 2025-06-01T20:56:27.4697882Z  Defines outputs of the github action that invokes this script 2025-06-01T20:56:27.4698585Z  """ 2025-06-01T20:56:27.4698997Z  if not GITHUB_OUTPUT: 2025-06-01T20:56:27.4700223Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:27.4701707Z  log.warning( 2025-06-01T20:56:27.4702704Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-06-01T20:56:27.4703745Z  ) 2025-06-01T20:56:27.4704240Z  print(f"::set-output name={key}::{value}") 2025-06-01T20:56:27.4704872Z  return 2025-06-01T20:56:27.4705311Z  2025-06-01T20:56:27.4705876Z  with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:27.4706545Z  log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:27.4707189Z  f.write(f"{key}={value}\n") 2025-06-01T20:56:27.4707731Z  2025-06-01T20:56:27.4708093Z  2025-06-01T20:56:27.4708652Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:27.4709389Z  return frozenset( 2025-06-01T20:56:27.4710110Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:27.4710980Z  ) 2025-06-01T20:56:27.4711402Z  2025-06-01T20:56:27.4711779Z  2025-06-01T20:56:27.4712184Z def parse_args() -> Any: 2025-06-01T20:56:27.4712874Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:27.4713865Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:27.4714736Z  parser.add_argument( 2025-06-01T20:56:27.4715271Z  "--github-issue-repo", 2025-06-01T20:56:27.4715821Z  type=str, 2025-06-01T20:56:27.4716309Z  required=False, 2025-06-01T20:56:27.4716967Z  default="pytorch/test-infra", 2025-06-01T20:56:27.4717604Z  help="GitHub repo to get the issue", 2025-06-01T20:56:27.4718174Z  ) 2025-06-01T20:56:27.4718587Z  parser.add_argument( 2025-06-01T20:56:27.4719103Z  "--github-repo", 2025-06-01T20:56:27.4719605Z  type=str, 2025-06-01T20:56:27.4720080Z  required=True, 2025-06-01T20:56:27.4720632Z  help="GitHub repo where CI is running", 2025-06-01T20:56:27.4721319Z  ) 2025-06-01T20:56:27.4721745Z  parser.add_argument( 2025-06-01T20:56:27.4722473Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:27.4723213Z  ) 2025-06-01T20:56:27.4723625Z  parser.add_argument( 2025-06-01T20:56:27.4724363Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:27.4725130Z  ) 2025-06-01T20:56:27.4725544Z  parser.add_argument( 2025-06-01T20:56:27.4726292Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:27.4727067Z  ) 2025-06-01T20:56:27.4727479Z  parser.add_argument( 2025-06-01T20:56:27.4728260Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:27.4729053Z  ) 2025-06-01T20:56:27.4729489Z  parser.add_argument( 2025-06-01T20:56:27.4730018Z  "--github-ref-type", 2025-06-01T20:56:27.4730542Z  type=str, 2025-06-01T20:56:27.4731113Z  required=True, 2025-06-01T20:56:27.4731710Z  help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:27.4732324Z  ) 2025-06-01T20:56:27.4732740Z  parser.add_argument( 2025-06-01T20:56:27.4733283Z  "--eligible-experiments", 2025-06-01T20:56:27.4733885Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:27.4734477Z  required=False, 2025-06-01T20:56:27.4734984Z  default="", 2025-06-01T20:56:27.4735978Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:27.4737026Z  ) 2025-06-01T20:56:27.4737435Z  parser.add_argument( 2025-06-01T20:56:27.4737981Z  "--opt-out-experiments", 2025-06-01T20:56:27.4738576Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:27.4739158Z  required=False, 2025-06-01T20:56:27.4739662Z  default="", 2025-06-01T20:56:27.4740256Z  help=( 2025-06-01T20:56:27.4741152Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:27.4742452Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:27.4743397Z  ), 2025-06-01T20:56:27.4743808Z  ) 2025-06-01T20:56:27.4744227Z  parser.add_argument( 2025-06-01T20:56:27.4744742Z  "--pr-number", 2025-06-01T20:56:27.4745235Z  type=str, 2025-06-01T20:56:27.4745706Z  required=False, 2025-06-01T20:56:27.4746202Z  default="", 2025-06-01T20:56:27.4746773Z  help="the optional PR number where this is run", 2025-06-01T20:56:27.4747391Z  ) 2025-06-01T20:56:27.4747774Z  2025-06-01T20:56:27.4748177Z  return parser.parse_args() 2025-06-01T20:56:27.4748711Z  2025-06-01T20:56:27.4749076Z  2025-06-01T20:56:27.4749752Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.4750740Z  auth = Auth.Token(github_token) 2025-06-01T20:56:27.4752102Z  return Github(auth=auth) 2025-06-01T20:56:27.4752631Z  2025-06-01T20:56:27.4752999Z  2025-06-01T20:56:27.4753732Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.4754651Z  repo = gh.get_repo(repo) 2025-06-01T20:56:27.4755248Z  return repo.get_issue(number=issue_num) 2025-06-01T20:56:27.4755833Z  2025-06-01T20:56:27.4756208Z  2025-06-01T20:56:27.4756605Z def get_potential_pr_author( 2025-06-01T20:56:27.4757372Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:27.4758132Z ) -> str: 2025-06-01T20:56:27.4758748Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:27.4759673Z  # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:27.4760539Z  # embedded in the tag name: ciflow// 2025-06-01T20:56:27.4761289Z  2025-06-01T20:56:27.4761703Z  gh = get_gh_client(github_token) 2025-06-01T20:56:27.4762250Z  2025-06-01T20:56:27.4762766Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:27.4763479Z  split_tag = ref_name.split("/") 2025-06-01T20:56:27.4764045Z  if ( 2025-06-01T20:56:27.4764489Z  len(split_tag) == 3 2025-06-01T20:56:27.4765060Z  and split_tag[0] == "ciflow" 2025-06-01T20:56:27.4765651Z  and split_tag[2].isnumeric() 2025-06-01T20:56:27.4766207Z  ): 2025-06-01T20:56:27.4766666Z  pr_number = split_tag[2] 2025-06-01T20:56:27.4767227Z  try: 2025-06-01T20:56:27.4767741Z  repository = gh.get_repo(repo) 2025-06-01T20:56:27.4768443Z  pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:27.4769136Z  except Exception as e: 2025-06-01T20:56:27.4769737Z  raise Exception( # noqa: TRY002 2025-06-01T20:56:27.4770506Z  f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:27.4771392Z  ) from e 2025-06-01T20:56:27.4772039Z  return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:27.4772864Z  # In all other cases, return the original input username 2025-06-01T20:56:27.4773525Z  return username 2025-06-01T20:56:27.4773993Z  2025-06-01T20:56:27.4774353Z  2025-06-01T20:56:27.4774971Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:27.4775565Z  """ 2025-06-01T20:56:27.4776321Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:27.4777201Z  """ 2025-06-01T20:56:27.4777833Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:27.4778581Z  2025-06-01T20:56:27.4778941Z  2025-06-01T20:56:27.4779365Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:27.4779926Z  try: 2025-06-01T20:56:27.4780382Z  data = yaml.safe_load(yaml_text) 2025-06-01T20:56:27.4781084Z  return data 2025-06-01T20:56:27.4781592Z  except yaml.YAMLError: 2025-06-01T20:56:27.4782181Z  log.exception("Error loading YAML") 2025-06-01T20:56:27.4782752Z  raise 2025-06-01T20:56:27.4783180Z  2025-06-01T20:56:27.4783546Z  2025-06-01T20:56:27.4784239Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:27.4785083Z  """ 2025-06-01T20:56:27.4785922Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:27.4786775Z  2025-06-01T20:56:27.4787375Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.4788258Z  and the text below is the list of opted in users. 2025-06-01T20:56:27.4788874Z  2025-06-01T20:56:27.4789515Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:27.4790300Z  """ 2025-06-01T20:56:27.4790815Z  rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:27.4791601Z  if len(rollout_state_parts) >= 2: 2025-06-01T20:56:27.4792376Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:27.4793218Z  else: 2025-06-01T20:56:27.4793656Z  return "", rollout_state 2025-06-01T20:56:27.4794176Z  2025-06-01T20:56:27.4794533Z  2025-06-01T20:56:27.4794972Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:27.4795538Z  """ 2025-06-01T20:56:27.4796169Z  Dictionary of users with a list of features they have opted into 2025-06-01T20:56:27.4796895Z  """ 2025-06-01T20:56:27.4797279Z  2025-06-01T20:56:27.4797633Z  2025-06-01T20:56:27.4798218Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:27.4798964Z  """ 2025-06-01T20:56:27.4799783Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-06-01T20:56:27.4800725Z  2025-06-01T20:56:27.4801735Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:27.4802881Z  - Example line: "@User1,lf,split_build" 2025-06-01T20:56:27.4803654Z  - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:27.4804356Z  2025-06-01T20:56:27.4804712Z  2025-06-01T20:56:27.4805067Z  """ 2025-06-01T20:56:27.4805485Z  optins = UserOptins() 2025-06-01T20:56:27.4806057Z  for user in user_optin_text.split("\n"): 2025-06-01T20:56:27.4806680Z  user = user.strip("\r\n\t -") 2025-06-01T20:56:27.4807305Z  if not user or not user.startswith("@"): 2025-06-01T20:56:27.4807926Z  # Not a valid user. Skip 2025-06-01T20:56:27.4808481Z  continue 2025-06-01T20:56:27.4808964Z  2025-06-01T20:56:27.4809342Z  if user: 2025-06-01T20:56:27.4809998Z  usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:27.4810775Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:27.4811597Z  2025-06-01T20:56:27.4811983Z  return optins 2025-06-01T20:56:27.4812436Z  2025-06-01T20:56:27.4812782Z  2025-06-01T20:56:27.4813325Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:27.4813999Z  """ 2025-06-01T20:56:27.4814460Z  Check if the experiment name is valid. 2025-06-01T20:56:27.4815040Z  A valid name: 2025-06-01T20:56:27.4815790Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:27.4816849Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:27.4817644Z  - Cannot contain spaces 2025-06-01T20:56:27.4818167Z  """ 2025-06-01T20:56:27.4818548Z  2025-06-01T20:56:27.4819050Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:27.4819860Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:27.4820722Z  2025-06-01T20:56:27.4821374Z  if valid: 2025-06-01T20:56:27.4821828Z  return True 2025-06-01T20:56:27.4822280Z  2025-06-01T20:56:27.4822645Z  log.error( 2025-06-01T20:56:27.4824309Z  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-06-01T20:56:27.4826058Z  ) 2025-06-01T20:56:27.4826451Z  return False 2025-06-01T20:56:27.4826901Z  2025-06-01T20:56:27.4827250Z  2025-06-01T20:56:27.4827809Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:27.4828514Z  """ 2025-06-01T20:56:27.4829183Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:27.4829981Z  """ 2025-06-01T20:56:27.4830373Z  try: 2025-06-01T20:56:27.4830779Z  if settings_text: 2025-06-01T20:56:27.4831722Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:27.4832606Z  # for easy reading 2025-06-01T20:56:27.4833525Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:27.4834529Z  # the backtick character in shell commands. 2025-06-01T20:56:27.4835209Z  backtick = chr(96) # backtick character 2025-06-01T20:56:27.4835964Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:27.4836723Z  settings = load_yaml(settings_text) 2025-06-01T20:56:27.4837286Z  2025-06-01T20:56:27.4837946Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:27.4838771Z  experiments = {} 2025-06-01T20:56:27.4839269Z  2025-06-01T20:56:27.4839885Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:27.4840738Z  if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:27.4842082Z  # 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-06-01T20:56:27.4843252Z  continue 2025-06-01T20:56:27.4843742Z  2025-06-01T20:56:27.4844145Z  valid_settings = {} 2025-06-01T20:56:27.4844869Z  for setting in exp_settings: 2025-06-01T20:56:27.4845515Z  if setting not in Experiment._fields: 2025-06-01T20:56:27.4846141Z  log.warning( 2025-06-01T20:56:27.4846955Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:27.4847746Z  ) 2025-06-01T20:56:27.4848239Z  else: 2025-06-01T20:56:27.4848841Z  valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:27.4849467Z  2025-06-01T20:56:27.4849985Z  experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:27.4850701Z  return Settings(experiments) 2025-06-01T20:56:27.4851353Z  2025-06-01T20:56:27.4851734Z  except Exception: 2025-06-01T20:56:27.4852311Z  log.exception("Failed to parse settings") 2025-06-01T20:56:27.4852906Z  2025-06-01T20:56:27.4853278Z  return Settings() 2025-06-01T20:56:27.4853743Z  2025-06-01T20:56:27.4854092Z  2025-06-01T20:56:27.4854710Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:27.4855348Z  """ 2025-06-01T20:56:27.4855845Z  Parse settings, if any, from the rollout state. 2025-06-01T20:56:27.4856449Z  2025-06-01T20:56:27.4857043Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.4857905Z  and the text below is the list of opted in users. 2025-06-01T20:56:27.4858512Z  2025-06-01T20:56:27.4859168Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:27.4859970Z  """ 2025-06-01T20:56:27.4860602Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.4861584Z  return parse_settings_from_text(settings_text) 2025-06-01T20:56:27.4862179Z  2025-06-01T20:56:27.4862543Z  2025-06-01T20:56:27.4863031Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:27.4863662Z  """ 2025-06-01T20:56:27.4864104Z  Parse users from the rollout state. 2025-06-01T20:56:27.4864668Z  2025-06-01T20:56:27.4865024Z  """ 2025-06-01T20:56:27.4865639Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.4866487Z  return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:27.4867087Z  2025-06-01T20:56:27.4867444Z  2025-06-01T20:56:27.4868123Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.4868956Z  """ 2025-06-01T20:56:27.4869435Z  Check if a user is opted into an experiment 2025-06-01T20:56:27.4870029Z  """ 2025-06-01T20:56:27.4870548Z  return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:27.4871284Z  2025-06-01T20:56:27.4871644Z  2025-06-01T20:56:27.4872347Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.4873204Z  """ 2025-06-01T20:56:27.4873726Z  Check if a user explicitly opted out of an experiment 2025-06-01T20:56:27.4874373Z  """ 2025-06-01T20:56:27.4874951Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:27.4875734Z  experiment_optout = "-" + experiment_name 2025-06-01T20:56:27.4876460Z  if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:27.4877124Z  return False 2025-06-01T20:56:27.4877578Z  2025-06-01T20:56:27.4878082Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:27.4878879Z  log.warning( 2025-06-01T20:56:27.4879822Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:27.4880797Z  ) 2025-06-01T20:56:27.4881308Z  2025-06-01T20:56:27.4881676Z  return True 2025-06-01T20:56:27.4882114Z  2025-06-01T20:56:27.4882463Z  2025-06-01T20:56:27.4882842Z def get_runner_prefix( 2025-06-01T20:56:27.4883359Z  rollout_state: str, 2025-06-01T20:56:27.4883900Z  workflow_requestors: Iterable[str], 2025-06-01T20:56:27.4884469Z  branch: str, 2025-06-01T20:56:27.4885055Z  eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.4885822Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.4886465Z  is_canary: bool = False, 2025-06-01T20:56:27.4886985Z ) -> str: 2025-06-01T20:56:27.4887459Z  settings = parse_settings(rollout_state) 2025-06-01T20:56:27.4888128Z  user_optins = parse_users(rollout_state) 2025-06-01T20:56:27.4888691Z  2025-06-01T20:56:27.4889187Z  fleet_prefix = "" 2025-06-01T20:56:27.4889692Z  prefixes = [] 2025-06-01T20:56:27.4890423Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:27.4891603Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:27.4892395Z  log.info( 2025-06-01T20:56:27.4893177Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:27.4894009Z  ) 2025-06-01T20:56:27.4894444Z  continue 2025-06-01T20:56:27.4894902Z  2025-06-01T20:56:27.4895295Z  if opt_out_experiments: 2025-06-01T20:56:27.4895954Z  if experiment_name in opt_out_experiments: 2025-06-01T20:56:27.4896674Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:27.4897337Z  log.info( 2025-06-01T20:56:27.4898393Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:27.4899477Z  ) 2025-06-01T20:56:27.4899945Z  continue 2025-06-01T20:56:27.4900420Z  2025-06-01T20:56:27.4900822Z  if eligible_experiments: 2025-06-01T20:56:27.4901567Z  if experiment_name not in eligible_experiments: 2025-06-01T20:56:27.4902288Z  exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:27.4902896Z  log.info( 2025-06-01T20:56:27.4903803Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:27.4904742Z  ) 2025-06-01T20:56:27.4905199Z  continue 2025-06-01T20:56:27.4905773Z  elif not experiment_settings.default: 2025-06-01T20:56:27.4906356Z  log.info( 2025-06-01T20:56:27.4907123Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:27.4907968Z  ) 2025-06-01T20:56:27.4908396Z  continue 2025-06-01T20:56:27.4908841Z  2025-06-01T20:56:27.4909343Z  # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:27.4910028Z  opted_out_users = [ 2025-06-01T20:56:27.4910545Z  requestor 2025-06-01T20:56:27.4911184Z  for requestor in workflow_requestors 2025-06-01T20:56:27.4912080Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.4912778Z  ] 2025-06-01T20:56:27.4913170Z  2025-06-01T20:56:27.4913550Z  if opted_out_users: 2025-06-01T20:56:27.4914089Z  log.info( 2025-06-01T20:56:27.4914828Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:27.4915601Z  ) 2025-06-01T20:56:27.4916040Z  continue 2025-06-01T20:56:27.4916496Z  2025-06-01T20:56:27.4917003Z  # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:27.4917675Z  opted_in_users = [ 2025-06-01T20:56:27.4918196Z  requestor 2025-06-01T20:56:27.4918736Z  for requestor in workflow_requestors 2025-06-01T20:56:27.4919488Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.4920176Z  ] 2025-06-01T20:56:27.4920574Z  2025-06-01T20:56:27.4921058Z  enabled = False 2025-06-01T20:56:27.4921570Z  if opted_in_users: 2025-06-01T20:56:27.4922202Z  log.info( 2025-06-01T20:56:27.4922914Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:27.4923674Z  ) 2025-06-01T20:56:27.4924114Z  enabled = True 2025-06-01T20:56:27.4924612Z  2025-06-01T20:56:27.4925050Z  elif experiment_settings.rollout_perc: 2025-06-01T20:56:27.4925991Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:27.4927052Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:27.4927781Z  log.info( 2025-06-01T20:56:27.4928786Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:27.4929804Z  ) 2025-06-01T20:56:27.4930287Z  enabled = True 2025-06-01T20:56:27.4930808Z  2025-06-01T20:56:27.4931280Z  if enabled: 2025-06-01T20:56:27.4931785Z  label = experiment_name 2025-06-01T20:56:27.4932421Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:27.4933347Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:27.4934332Z  # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:27.4935194Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:27.4935938Z  if is_canary: 2025-06-01T20:56:27.4936516Z  label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:27.4937132Z  fleet_prefix = label 2025-06-01T20:56:27.4937687Z  else: 2025-06-01T20:56:27.4938201Z  prefixes.append(label) 2025-06-01T20:56:27.4938759Z  2025-06-01T20:56:27.4939142Z  if len(prefixes) > 1: 2025-06-01T20:56:27.4939657Z  log.error( 2025-06-01T20:56:27.4940942Z  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-06-01T20:56:27.4942204Z  ) 2025-06-01T20:56:27.4942646Z  prefixes = prefixes[:1] 2025-06-01T20:56:27.4943174Z  2025-06-01T20:56:27.4943573Z  # Fleet always comes first 2025-06-01T20:56:27.4944109Z  if fleet_prefix: 2025-06-01T20:56:27.4944703Z  prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:27.4945391Z  2025-06-01T20:56:27.4945888Z  return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:27.4946509Z  2025-06-01T20:56:27.4946864Z  2025-06-01T20:56:27.4947572Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:27.4948437Z  """ 2025-06-01T20:56:27.4949103Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:27.4949884Z  2025-06-01T20:56:27.4950515Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:27.4951388Z  """ 2025-06-01T20:56:27.4951831Z  gh = get_gh_client(github_token) 2025-06-01T20:56:27.4952456Z  issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:27.4953182Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:27.4953852Z  2025-06-01T20:56:27.4954208Z  2025-06-01T20:56:27.4954871Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:27.4955836Z  for _ in range(num_retries): 2025-06-01T20:56:27.4956379Z  try: 2025-06-01T20:56:27.4956881Z  req = Request(url=url, headers=headers) 2025-06-01T20:56:27.4957624Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:27.4958349Z  return json.loads(content) 2025-06-01T20:56:27.4958928Z  except Exception as e: 2025-06-01T20:56:27.4959555Z  log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:27.4960225Z  2025-06-01T20:56:27.4960958Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:27.4961758Z  return {} 2025-06-01T20:56:27.4962195Z  2025-06-01T20:56:27.4962552Z  2025-06-01T20:56:27.4962917Z @cache 2025-06-01T20:56:27.4963629Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:27.4964470Z  """ 2025-06-01T20:56:27.4964916Z  Dynamically get PR information 2025-06-01T20:56:27.4965460Z  """ 2025-06-01T20:56:27.4966022Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:27.4966714Z  headers = { 2025-06-01T20:56:27.4967248Z  "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:27.4967934Z  "Authorization": f"token {github_token}", 2025-06-01T20:56:27.4968515Z  } 2025-06-01T20:56:27.4969001Z  json_response: dict[str, Any] = download_json( 2025-06-01T20:56:27.4969677Z  url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:27.4970285Z  headers=headers, 2025-06-01T20:56:27.4970774Z  ) 2025-06-01T20:56:27.4971256Z  2025-06-01T20:56:27.4971640Z  if not json_response: 2025-06-01T20:56:27.4972316Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:27.4973015Z  return {} 2025-06-01T20:56:27.4973460Z  2025-06-01T20:56:27.4973850Z  return json_response 2025-06-01T20:56:27.4974331Z  2025-06-01T20:56:27.4974685Z  2025-06-01T20:56:27.4975326Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:27.4976135Z  """ 2025-06-01T20:56:27.4976733Z  Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:27.4977450Z  """ 2025-06-01T20:56:27.4977998Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:27.4978675Z  return { 2025-06-01T20:56:27.4979467Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:27.4980220Z  } 2025-06-01T20:56:27.4980606Z  2025-06-01T20:56:27.4981058Z  2025-06-01T20:56:27.4981445Z def main() -> None: 2025-06-01T20:56:27.4981933Z  args = parse_args() 2025-06-01T20:56:27.4982408Z  2025-06-01T20:56:27.4982862Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:27.4983447Z  2025-06-01T20:56:27.4983847Z  # Check if the PR is opt-out 2025-06-01T20:56:27.4984402Z  if args.pr_number: 2025-06-01T20:56:27.4985166Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:27.4986015Z  if OPT_OUT_LABEL in labels: 2025-06-01T20:56:27.4986567Z  log.info( 2025-06-01T20:56:27.4987366Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:27.4988216Z  ) 2025-06-01T20:56:27.4988856Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.4989607Z  sys.exit() 2025-06-01T20:56:27.4990212Z  2025-06-01T20:56:27.4990576Z  try: 2025-06-01T20:56:27.4991182Z  rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:27.4991983Z  args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:27.4992696Z  ) 2025-06-01T20:56:27.4993092Z  2025-06-01T20:56:27.4993514Z  username = get_potential_pr_author( 2025-06-01T20:56:27.4994110Z  args.github_token, 2025-06-01T20:56:27.4994657Z  args.github_repo, 2025-06-01T20:56:27.4995202Z  args.github_actor, 2025-06-01T20:56:27.4995780Z  args.github_ref_type, 2025-06-01T20:56:27.4996353Z  args.github_branch, 2025-06-01T20:56:27.4996875Z  ) 2025-06-01T20:56:27.4997275Z  2025-06-01T20:56:27.4997797Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:27.4998456Z  2025-06-01T20:56:27.4998904Z  runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:27.4999506Z  rollout_state, 2025-06-01T20:56:27.5000076Z  (args.github_issue_owner, username), 2025-06-01T20:56:27.5000680Z  args.github_branch, 2025-06-01T20:56:27.5001347Z  args.eligible_experiments, 2025-06-01T20:56:27.5001947Z  args.opt_out_experiments, 2025-06-01T20:56:27.5002513Z  is_canary, 2025-06-01T20:56:27.5002986Z  ) 2025-06-01T20:56:27.5003381Z  2025-06-01T20:56:27.5003774Z  except Exception as e: 2025-06-01T20:56:27.5004302Z  log.error( 2025-06-01T20:56:27.5005076Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:27.5005920Z  ) 2025-06-01T20:56:27.5006324Z  2025-06-01T20:56:27.5006905Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.5007624Z  2025-06-01T20:56:27.5007983Z  2025-06-01T20:56:27.5008366Z if __name__ == "__main__": 2025-06-01T20:56:27.5008869Z  main() 2025-06-01T20:56:27.5009277Z  2025-06-01T20:56:27.5009631Z EOF 2025-06-01T20:56:27.5010001Z  2025-06-01T20:56:27.5010392Z cat runner_determinator.py 2025-06-01T20:56:27.5402748Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:27.5403680Z env: 2025-06-01T20:56:27.5404435Z GITHUB_TOKEN: *** 2025-06-01T20:56:27.5404871Z ISSUE_NUMBER: 5132 2025-06-01T20:56:27.5405325Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:27.5406068Z ISSUE_OWNER: 2025-06-01T20:56:27.5406464Z CHECK_EXPERIMENTS: 2025-06-01T20:56:27.5406903Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:27.5407327Z PR_NUMBER: 2025-06-01T20:56:27.5407714Z ##[endgroup] 2025-06-01T20:56:27.5631862Z # flake8: noqa: G004 2025-06-01T20:56:27.5632205Z 2025-06-01T20:56:27.5632658Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:27.5633676Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:27.5634508Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:27.5634974Z 2025-06-01T20:56:27.5635130Z """ 2025-06-01T20:56:27.5635722Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:27.5636634Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:27.5637591Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:27.5638461Z of which runners should be used to run which job. 2025-06-01T20:56:27.5638885Z 2025-06-01T20:56:27.5639282Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:27.5640402Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:27.5641608Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:27.5642342Z list, defined. 2025-06-01T20:56:27.5642572Z 2025-06-01T20:56:27.5642944Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:27.5643910Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:27.5644764Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:27.5645228Z 2025-06-01T20:56:27.5645606Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:27.5646514Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:27.5647284Z experiments which the user could be opted in to. 2025-06-01T20:56:27.5647704Z 2025-06-01T20:56:27.5647908Z The user list has the following rules: 2025-06-01T20:56:27.5648266Z 2025-06-01T20:56:27.5648587Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:27.5649471Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:27.5650261Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:27.5650669Z 2025-06-01T20:56:27.5650837Z Example config: 2025-06-01T20:56:27.5651483Z # A list of experiments that can be opted into. 2025-06-01T20:56:27.5652167Z # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:27.5652824Z # Expected syntax is: 2025-06-01T20:56:27.5653525Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:27.5654551Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:27.5655206Z 2025-06-01T20:56:27.5655382Z experiments: 2025-06-01T20:56:27.5655769Z lf: 2025-06-01T20:56:27.5656146Z rollout_percent: 25 2025-06-01T20:56:27.5656606Z all_branches: false 2025-06-01T20:56:27.5657067Z default: true 2025-06-01T20:56:27.5657473Z --- 2025-06-01T20:56:27.5657681Z 2025-06-01T20:56:27.5657849Z # Opt-ins: 2025-06-01T20:56:27.5658423Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:27.5659321Z # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:27.5660126Z # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:27.5660807Z # Experiments should be from the above list. 2025-06-01T20:56:27.5661387Z 2025-06-01T20:56:27.5661575Z @User1,-lf,split_build 2025-06-01T20:56:27.5662015Z @User2,lf 2025-06-01T20:56:27.5662398Z @User3,split_build 2025-06-01T20:56:27.5662804Z """ 2025-06-01T20:56:27.5663156Z 2025-06-01T20:56:27.5663318Z import json 2025-06-01T20:56:27.5663693Z import logging 2025-06-01T20:56:27.5664070Z import os 2025-06-01T20:56:27.5664430Z import random 2025-06-01T20:56:27.5664802Z import re 2025-06-01T20:56:27.5665154Z import sys 2025-06-01T20:56:27.5665562Z from argparse import ArgumentParser 2025-06-01T20:56:27.5666101Z from collections.abc import Iterable 2025-06-01T20:56:27.5666624Z from functools import cache 2025-06-01T20:56:27.5667098Z from logging import LogRecord 2025-06-01T20:56:27.5667585Z from typing import Any, NamedTuple 2025-06-01T20:56:27.5668126Z from urllib.request import Request, urlopen 2025-06-01T20:56:27.5668504Z 2025-06-01T20:56:27.5668667Z import yaml 2025-06-01T20:56:27.5669096Z from github import Auth, Github 2025-06-01T20:56:27.5669580Z from github.Issue import Issue 2025-06-01T20:56:27.5669895Z 2025-06-01T20:56:27.5669902Z 2025-06-01T20:56:27.5670117Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:27.5670823Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:27.5671938Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:27.5672548Z 2025-06-01T20:56:27.5672777Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:27.5673489Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:27.5674031Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:27.5674588Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:27.5674960Z 2025-06-01T20:56:27.5675158Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:27.5675493Z 2025-06-01T20:56:27.5675681Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:27.5676137Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:27.5676418Z 2025-06-01T20:56:27.5676430Z 2025-06-01T20:56:27.5676613Z class Experiment(NamedTuple): 2025-06-01T20:56:27.5677095Z rollout_perc: float = ( 2025-06-01T20:56:27.5677741Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:27.5678451Z ) 2025-06-01T20:56:27.5678830Z all_branches: bool = ( 2025-06-01T20:56:27.5679470Z False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:27.5680168Z ) 2025-06-01T20:56:27.5680530Z default: bool = ( 2025-06-01T20:56:27.5681333Z True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:27.5682013Z ) 2025-06-01T20:56:27.5682210Z 2025-06-01T20:56:27.5682389Z # Add more fields as needed 2025-06-01T20:56:27.5682703Z 2025-06-01T20:56:27.5682710Z 2025-06-01T20:56:27.5682894Z class Settings(NamedTuple): 2025-06-01T20:56:27.5683338Z """ 2025-06-01T20:56:27.5683797Z Settings for the experiments that can be opted into. 2025-06-01T20:56:27.5684380Z """ 2025-06-01T20:56:27.5684574Z 2025-06-01T20:56:27.5684776Z experiments: dict[str, Experiment] = {} 2025-06-01T20:56:27.5685151Z 2025-06-01T20:56:27.5685165Z 2025-06-01T20:56:27.5685371Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:27.5685996Z """Color codes the log messages based on the log level""" 2025-06-01T20:56:27.5686462Z 2025-06-01T20:56:27.5686624Z COLORS = { 2025-06-01T20:56:27.5687026Z "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:27.5687539Z "ERROR": "\033[31m", # Red 2025-06-01T20:56:27.5688038Z "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:27.5688541Z "INFO": "\033[0m", # Reset 2025-06-01T20:56:27.5689028Z "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:27.5689499Z } 2025-06-01T20:56:27.5689700Z 2025-06-01T20:56:27.5689915Z def format(self, record: LogRecord) -> str: 2025-06-01T20:56:27.5690695Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:27.5691706Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:27.5692309Z return super().format(record) 2025-06-01T20:56:27.5692655Z 2025-06-01T20:56:27.5692663Z 2025-06-01T20:56:27.5692853Z handler = logging.StreamHandler() 2025-06-01T20:56:27.5693731Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:27.5694320Z 2025-06-01T20:56:27.5694563Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:27.5695164Z log.addHandler(handler) 2025-06-01T20:56:27.5695612Z log.setLevel(logging.INFO) 2025-06-01T20:56:27.5695901Z 2025-06-01T20:56:27.5695908Z 2025-06-01T20:56:27.5696150Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:27.5696730Z """ 2025-06-01T20:56:27.5697231Z Defines outputs of the github action that invokes this script 2025-06-01T20:56:27.5697881Z """ 2025-06-01T20:56:27.5698239Z if not GITHUB_OUTPUT: 2025-06-01T20:56:27.5699368Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:27.5700564Z log.warning( 2025-06-01T20:56:27.5701655Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-06-01T20:56:27.5702662Z ) 2025-06-01T20:56:27.5712810Z print(f"::set-output name={key}::{value}") 2025-06-01T20:56:27.5713457Z return 2025-06-01T20:56:27.5713694Z 2025-06-01T20:56:27.5714078Z with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:27.5714728Z log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:27.5715317Z f.write(f"{key}={value}\n") 2025-06-01T20:56:27.5715661Z 2025-06-01T20:56:27.5715667Z 2025-06-01T20:56:27.5715970Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:27.5716633Z return frozenset( 2025-06-01T20:56:27.5717251Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:27.5717979Z ) 2025-06-01T20:56:27.5718176Z 2025-06-01T20:56:27.5718184Z 2025-06-01T20:56:27.5718364Z def parse_args() -> Any: 2025-06-01T20:56:27.5718925Z parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:27.5719834Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:27.5720653Z parser.add_argument( 2025-06-01T20:56:27.5721417Z "--github-issue-repo", 2025-06-01T20:56:27.5721904Z type=str, 2025-06-01T20:56:27.5722321Z required=False, 2025-06-01T20:56:27.5722775Z default="pytorch/test-infra", 2025-06-01T20:56:27.5723320Z help="GitHub repo to get the issue", 2025-06-01T20:56:27.5723841Z ) 2025-06-01T20:56:27.5724206Z parser.add_argument( 2025-06-01T20:56:27.5724644Z "--github-repo", 2025-06-01T20:56:27.5725072Z type=str, 2025-06-01T20:56:27.5725461Z required=True, 2025-06-01T20:56:27.5725921Z help="GitHub repo where CI is running", 2025-06-01T20:56:27.5726463Z ) 2025-06-01T20:56:27.5726832Z parser.add_argument( 2025-06-01T20:56:27.5727449Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:27.5728124Z ) 2025-06-01T20:56:27.5728485Z parser.add_argument( 2025-06-01T20:56:27.5729110Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:27.5729813Z ) 2025-06-01T20:56:27.5730170Z parser.add_argument( 2025-06-01T20:56:27.5730819Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:27.5731749Z ) 2025-06-01T20:56:27.5732117Z parser.add_argument( 2025-06-01T20:56:27.5732775Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:27.5733514Z ) 2025-06-01T20:56:27.5733881Z parser.add_argument( 2025-06-01T20:56:27.5734323Z "--github-ref-type", 2025-06-01T20:56:27.5734778Z type=str, 2025-06-01T20:56:27.5735160Z required=True, 2025-06-01T20:56:27.5735636Z help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:27.5736197Z ) 2025-06-01T20:56:27.5736560Z parser.add_argument( 2025-06-01T20:56:27.5737011Z "--eligible-experiments", 2025-06-01T20:56:27.5737690Z type=_str_comma_separated_to_set, 2025-06-01T20:56:27.5738214Z required=False, 2025-06-01T20:56:27.5738628Z default="", 2025-06-01T20:56:27.5739518Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:27.5740495Z ) 2025-06-01T20:56:27.5741025Z parser.add_argument( 2025-06-01T20:56:27.5741532Z "--opt-out-experiments", 2025-06-01T20:56:27.5742039Z type=_str_comma_separated_to_set, 2025-06-01T20:56:27.5742568Z required=False, 2025-06-01T20:56:27.5742987Z default="", 2025-06-01T20:56:27.5743361Z help=( 2025-06-01T20:56:27.5744047Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:27.5745249Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:27.5746123Z ), 2025-06-01T20:56:27.5746490Z ) 2025-06-01T20:56:27.5746844Z parser.add_argument( 2025-06-01T20:56:27.5747277Z "--pr-number", 2025-06-01T20:56:27.5747678Z type=str, 2025-06-01T20:56:27.5748072Z required=False, 2025-06-01T20:56:27.5798768Z default="", 2025-06-01T20:56:27.5799594Z help="the optional PR number where this is run", 2025-06-01T20:56:27.5800217Z ) 2025-06-01T20:56:27.5800437Z 2025-06-01T20:56:27.5800631Z return parser.parse_args() 2025-06-01T20:56:27.5801091Z 2025-06-01T20:56:27.5801099Z 2025-06-01T20:56:27.5801542Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.5802338Z auth = Auth.Token(github_token) 2025-06-01T20:56:27.5802852Z return Github(auth=auth) 2025-06-01T20:56:27.5803152Z 2025-06-01T20:56:27.5803159Z 2025-06-01T20:56:27.5803631Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.5804466Z repo = gh.get_repo(repo) 2025-06-01T20:56:27.5804970Z return repo.get_issue(number=issue_num) 2025-06-01T20:56:27.5805352Z 2025-06-01T20:56:27.5805359Z 2025-06-01T20:56:27.5805545Z def get_potential_pr_author( 2025-06-01T20:56:27.5806207Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:27.5806903Z ) -> str: 2025-06-01T20:56:27.5807438Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:27.5808261Z # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:27.5809037Z # embedded in the tag name: ciflow// 2025-06-01T20:56:27.5809476Z 2025-06-01T20:56:27.5809672Z gh = get_gh_client(github_token) 2025-06-01T20:56:27.5810009Z 2025-06-01T20:56:27.5810274Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:27.5811034Z split_tag = ref_name.split("/") 2025-06-01T20:56:27.5811542Z if ( 2025-06-01T20:56:27.5811923Z len(split_tag) == 3 2025-06-01T20:56:27.5812403Z and split_tag[0] == "ciflow" 2025-06-01T20:56:27.5812934Z and split_tag[2].isnumeric() 2025-06-01T20:56:27.5813439Z ): 2025-06-01T20:56:27.5813831Z pr_number = split_tag[2] 2025-06-01T20:56:27.5814315Z try: 2025-06-01T20:56:27.5814745Z repository = gh.get_repo(repo) 2025-06-01T20:56:27.5815366Z pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:27.5815969Z except Exception as e: 2025-06-01T20:56:27.5816493Z raise Exception( # noqa: TRY002 2025-06-01T20:56:27.5817173Z f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:27.5817832Z ) from e 2025-06-01T20:56:27.5818361Z return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:27.5819077Z # In all other cases, return the original input username 2025-06-01T20:56:27.5819683Z return username 2025-06-01T20:56:27.5819922Z 2025-06-01T20:56:27.5820055Z 2025-06-01T20:56:27.5820283Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:27.5820840Z """ 2025-06-01T20:56:27.5821614Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:27.5822467Z """ 2025-06-01T20:56:27.5823023Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:27.5823584Z 2025-06-01T20:56:27.5823591Z 2025-06-01T20:56:27.5823780Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:27.5824281Z try: 2025-06-01T20:56:27.5824662Z data = yaml.safe_load(yaml_text) 2025-06-01T20:56:27.5825181Z return data 2025-06-01T20:56:27.5825586Z except yaml.YAMLError: 2025-06-01T20:56:27.5826070Z log.exception("Error loading YAML") 2025-06-01T20:56:27.5826591Z raise 2025-06-01T20:56:27.5826807Z 2025-06-01T20:56:27.5826815Z 2025-06-01T20:56:27.5827249Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:27.5828031Z """ 2025-06-01T20:56:27.5828669Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:27.5829307Z 2025-06-01T20:56:27.5829812Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.5830603Z and the text below is the list of opted in users. 2025-06-01T20:56:27.5831144Z 2025-06-01T20:56:27.5831524Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:27.5832255Z """ 2025-06-01T20:56:27.5832695Z rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:27.5833320Z if len(rollout_state_parts) >= 2: 2025-06-01T20:56:27.5833936Z return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:27.5834539Z else: 2025-06-01T20:56:27.5834916Z return "", rollout_state 2025-06-01T20:56:27.5835232Z 2025-06-01T20:56:27.5835240Z 2025-06-01T20:56:27.5835442Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:27.5835956Z """ 2025-06-01T20:56:27.5836482Z Dictionary of users with a list of features they have opted into 2025-06-01T20:56:27.5837147Z """ 2025-06-01T20:56:27.5837346Z 2025-06-01T20:56:27.5837353Z 2025-06-01T20:56:27.5837703Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:27.5838388Z """ 2025-06-01T20:56:27.5839112Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-06-01T20:56:27.5839841Z 2025-06-01T20:56:27.5840497Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:27.5841635Z - Example line: "@User1,lf,split_build" 2025-06-01T20:56:27.5842335Z - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:27.5842833Z 2025-06-01T20:56:27.5842840Z 2025-06-01T20:56:27.5842998Z """ 2025-06-01T20:56:27.5843359Z optins = UserOptins() 2025-06-01T20:56:27.5843846Z for user in user_optin_text.split("\n"): 2025-06-01T20:56:27.5844407Z user = user.strip("\r\n\t -") 2025-06-01T20:56:27.5844968Z if not user or not user.startswith("@"): 2025-06-01T20:56:27.5846027Z # Not a valid user. Skip 2025-06-01T20:56:27.5846943Z continue 2025-06-01T20:56:27.5847455Z 2025-06-01T20:56:27.5847733Z if user: 2025-06-01T20:56:27.5848483Z usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:27.5849926Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:27.5851594Z 2025-06-01T20:56:27.5851938Z return optins 2025-06-01T20:56:27.5852387Z 2025-06-01T20:56:27.5852409Z 2025-06-01T20:56:27.5852920Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:27.5854057Z """ 2025-06-01T20:56:27.5854708Z Check if the experiment name is valid. 2025-06-01T20:56:27.5855620Z A valid name: 2025-06-01T20:56:27.5857184Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:27.5859023Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:27.5860442Z - Cannot contain spaces 2025-06-01T20:56:27.5861600Z """ 2025-06-01T20:56:27.5862015Z 2025-06-01T20:56:27.5862490Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:27.5863785Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:27.5864630Z 2025-06-01T20:56:27.5864933Z if valid: 2025-06-01T20:56:27.5865683Z return True 2025-06-01T20:56:27.5866156Z 2025-06-01T20:56:27.5866473Z log.error( 2025-06-01T20:56:27.5869301Z 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-06-01T20:56:27.5872685Z ) 2025-06-01T20:56:27.5873375Z return False 2025-06-01T20:56:27.5873829Z 2025-06-01T20:56:27.5873843Z 2025-06-01T20:56:27.5874432Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:27.5875704Z """ 2025-06-01T20:56:27.5877023Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:27.5878241Z """ 2025-06-01T20:56:27.5878595Z try: 2025-06-01T20:56:27.5878978Z if settings_text: 2025-06-01T20:56:27.5879715Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:27.5880544Z # for easy reading 2025-06-01T20:56:27.5881569Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:27.5882496Z # the backtick character in shell commands. 2025-06-01T20:56:27.5883111Z backtick = chr(96) # backtick character 2025-06-01T20:56:27.5883781Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:27.5884484Z settings = load_yaml(settings_text) 2025-06-01T20:56:27.5884874Z 2025-06-01T20:56:27.5885309Z # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:27.5886092Z experiments = {} 2025-06-01T20:56:27.5886391Z 2025-06-01T20:56:27.5886781Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:27.5887567Z if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:27.5888735Z # 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-06-01T20:56:27.5889831Z continue 2025-06-01T20:56:27.5890128Z 2025-06-01T20:56:27.5890311Z valid_settings = {} 2025-06-01T20:56:27.5890826Z for setting in exp_settings: 2025-06-01T20:56:27.5891658Z if setting not in Experiment._fields: 2025-06-01T20:56:27.5892242Z log.warning( 2025-06-01T20:56:27.5892952Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:27.5893698Z ) 2025-06-01T20:56:27.5894122Z else: 2025-06-01T20:56:27.5894634Z valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:27.5895070Z 2025-06-01T20:56:27.5895346Z experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:27.5895990Z return Settings(experiments) 2025-06-01T20:56:27.5896349Z 2025-06-01T20:56:27.5896524Z except Exception: 2025-06-01T20:56:27.5896984Z log.exception("Failed to parse settings") 2025-06-01T20:56:27.5897381Z 2025-06-01T20:56:27.5897542Z return Settings() 2025-06-01T20:56:27.5897792Z 2025-06-01T20:56:27.5897798Z 2025-06-01T20:56:27.5898046Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:27.5898777Z """ 2025-06-01T20:56:27.5899212Z Parse settings, if any, from the rollout state. 2025-06-01T20:56:27.5899626Z 2025-06-01T20:56:27.5899979Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.5900755Z and the text below is the list of opted in users. 2025-06-01T20:56:27.5901360Z 2025-06-01T20:56:27.5901789Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:27.5902555Z """ 2025-06-01T20:56:27.5903108Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.5903889Z return parse_settings_from_text(settings_text) 2025-06-01T20:56:27.5904301Z 2025-06-01T20:56:27.5904308Z 2025-06-01T20:56:27.5904558Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:27.5905121Z """ 2025-06-01T20:56:27.5905507Z Parse users from the rollout state. 2025-06-01T20:56:27.5905864Z 2025-06-01T20:56:27.5906026Z """ 2025-06-01T20:56:27.5906546Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.5907303Z return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:27.5908528Z 2025-06-01T20:56:27.5908537Z 2025-06-01T20:56:27.5909139Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.5909948Z """ 2025-06-01T20:56:27.5910349Z Check if a user is opted into an experiment 2025-06-01T20:56:27.5911078Z """ 2025-06-01T20:56:27.5911542Z return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:27.5912164Z 2025-06-01T20:56:27.5912172Z 2025-06-01T20:56:27.5912625Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.5913569Z """ 2025-06-01T20:56:27.5914018Z Check if a user explicitly opted out of an experiment 2025-06-01T20:56:27.5914607Z """ 2025-06-01T20:56:27.5915104Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:27.5915819Z experiment_optout = "-" + experiment_name 2025-06-01T20:56:27.5916456Z if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:27.5917079Z return False 2025-06-01T20:56:27.5917331Z 2025-06-01T20:56:27.5917604Z if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:27.5918221Z log.warning( 2025-06-01T20:56:27.5919036Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:27.5919945Z ) 2025-06-01T20:56:27.5920144Z 2025-06-01T20:56:27.5920311Z return True 2025-06-01T20:56:27.5920534Z 2025-06-01T20:56:27.5920541Z 2025-06-01T20:56:27.5920709Z def get_runner_prefix( 2025-06-01T20:56:27.5921335Z rollout_state: str, 2025-06-01T20:56:27.5921783Z workflow_requestors: Iterable[str], 2025-06-01T20:56:27.5922302Z branch: str, 2025-06-01T20:56:27.5922784Z eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.5923461Z opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.5924071Z is_canary: bool = False, 2025-06-01T20:56:27.5924522Z ) -> str: 2025-06-01T20:56:27.5924927Z settings = parse_settings(rollout_state) 2025-06-01T20:56:27.5925505Z user_optins = parse_users(rollout_state) 2025-06-01T20:56:27.5925886Z 2025-06-01T20:56:27.5926054Z fleet_prefix = "" 2025-06-01T20:56:27.5926463Z prefixes = [] 2025-06-01T20:56:27.5927086Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:27.5928056Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:27.5928794Z log.info( 2025-06-01T20:56:27.5929472Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:27.5930249Z ) 2025-06-01T20:56:27.5930613Z continue 2025-06-01T20:56:27.5931215Z 2025-06-01T20:56:27.5931411Z if opt_out_experiments: 2025-06-01T20:56:27.5932116Z if experiment_name in opt_out_experiments: 2025-06-01T20:56:27.5932764Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:27.5933369Z log.info( 2025-06-01T20:56:27.5934334Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:27.5935355Z ) 2025-06-01T20:56:27.5935741Z continue 2025-06-01T20:56:27.5936009Z 2025-06-01T20:56:27.5936192Z if eligible_experiments: 2025-06-01T20:56:27.5936770Z if experiment_name not in eligible_experiments: 2025-06-01T20:56:27.5937408Z exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:27.5937975Z log.info( 2025-06-01T20:56:27.5938779Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:27.5939663Z ) 2025-06-01T20:56:27.5940048Z continue 2025-06-01T20:56:27.5940515Z elif not experiment_settings.default: 2025-06-01T20:56:27.5941268Z log.info( 2025-06-01T20:56:27.5942061Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:27.5942829Z ) 2025-06-01T20:56:27.5943190Z continue 2025-06-01T20:56:27.5943432Z 2025-06-01T20:56:27.5943710Z # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:27.5944330Z opted_out_users = [ 2025-06-01T20:56:27.5944772Z requestor 2025-06-01T20:56:27.5945210Z for requestor in workflow_requestors 2025-06-01T20:56:27.5945883Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.5946527Z ] 2025-06-01T20:56:27.5946725Z 2025-06-01T20:56:27.5946898Z if opted_out_users: 2025-06-01T20:56:27.5947349Z log.info( 2025-06-01T20:56:27.5947971Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:27.5948691Z ) 2025-06-01T20:56:27.5949055Z continue 2025-06-01T20:56:27.5949306Z 2025-06-01T20:56:27.5949581Z # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:27.5950220Z opted_in_users = [ 2025-06-01T20:56:27.5950673Z requestor 2025-06-01T20:56:27.5951336Z for requestor in workflow_requestors 2025-06-01T20:56:27.5952053Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.5952690Z ] 2025-06-01T20:56:27.5952893Z 2025-06-01T20:56:27.5953062Z enabled = False 2025-06-01T20:56:27.5953525Z if opted_in_users: 2025-06-01T20:56:27.5953962Z log.info( 2025-06-01T20:56:27.5954564Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:27.5955266Z ) 2025-06-01T20:56:27.5955654Z enabled = True 2025-06-01T20:56:27.5955931Z 2025-06-01T20:56:27.5956147Z elif experiment_settings.rollout_perc: 2025-06-01T20:56:27.5957003Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:27.5957977Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:27.5958641Z log.info( 2025-06-01T20:56:27.5959539Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:27.5961010Z ) 2025-06-01T20:56:27.5961426Z enabled = True 2025-06-01T20:56:27.5961726Z 2025-06-01T20:56:27.5961892Z if enabled: 2025-06-01T20:56:27.5962300Z label = experiment_name 2025-06-01T20:56:27.5962855Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:27.5963717Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:27.5965194Z # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:27.5966003Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:27.5966700Z if is_canary: 2025-06-01T20:56:27.5967215Z label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:27.5967781Z fleet_prefix = label 2025-06-01T20:56:27.5968283Z else: 2025-06-01T20:56:27.5968697Z prefixes.append(label) 2025-06-01T20:56:27.5969064Z 2025-06-01T20:56:27.5969244Z if len(prefixes) > 1: 2025-06-01T20:56:27.5969679Z log.error( 2025-06-01T20:56:27.5970773Z 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-06-01T20:56:27.5972086Z ) 2025-06-01T20:56:27.5972464Z prefixes = prefixes[:1] 2025-06-01T20:56:27.5972786Z 2025-06-01T20:56:27.5972975Z # Fleet always comes first 2025-06-01T20:56:27.5973437Z if fleet_prefix: 2025-06-01T20:56:27.5973877Z prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:27.5974250Z 2025-06-01T20:56:27.5974617Z return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:27.5975058Z 2025-06-01T20:56:27.5975064Z 2025-06-01T20:56:27.5975526Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:27.5976339Z """ 2025-06-01T20:56:27.5976922Z Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:27.5977517Z 2025-06-01T20:56:27.5977917Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:27.5978645Z """ 2025-06-01T20:56:27.5979026Z gh = get_gh_client(github_token) 2025-06-01T20:56:27.5979571Z issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:27.5980212Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:27.5980678Z 2025-06-01T20:56:27.5980686Z 2025-06-01T20:56:27.5981219Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:27.5982018Z for _ in range(num_retries): 2025-06-01T20:56:27.5982503Z try: 2025-06-01T20:56:27.5982920Z req = Request(url=url, headers=headers) 2025-06-01T20:56:27.5983595Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:27.5984245Z return json.loads(content) 2025-06-01T20:56:27.5984778Z except Exception as e: 2025-06-01T20:56:27.5985305Z log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:27.5985724Z 2025-06-01T20:56:27.5986103Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:27.5986839Z return {} 2025-06-01T20:56:27.5987058Z 2025-06-01T20:56:27.5987065Z 2025-06-01T20:56:27.5987216Z @cache 2025-06-01T20:56:27.5987840Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:27.5988629Z """ 2025-06-01T20:56:27.5989012Z Dynamically get PR information 2025-06-01T20:56:27.5989496Z """ 2025-06-01T20:56:27.5989986Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:27.5990627Z headers = { 2025-06-01T20:56:27.5991169Z "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:27.5991790Z "Authorization": f"token {github_token}", 2025-06-01T20:56:27.5992330Z } 2025-06-01T20:56:27.5992751Z json_response: dict[str, Any] = download_json( 2025-06-01T20:56:27.5993364Z url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:27.5993917Z headers=headers, 2025-06-01T20:56:27.5994336Z ) 2025-06-01T20:56:27.5994543Z 2025-06-01T20:56:27.5994725Z if not json_response: 2025-06-01T20:56:27.5995284Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:27.5995915Z return {} 2025-06-01T20:56:27.5996317Z 2025-06-01T20:56:27.5996501Z return json_response 2025-06-01T20:56:27.5996780Z 2025-06-01T20:56:27.5996788Z 2025-06-01T20:56:27.5997193Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:27.5997956Z """ 2025-06-01T20:56:27.5998479Z Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:27.5999155Z """ 2025-06-01T20:56:27.5999626Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:27.6000253Z return { 2025-06-01T20:56:27.6000944Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:27.6001678Z } 2025-06-01T20:56:27.6001873Z 2025-06-01T20:56:27.6001881Z 2025-06-01T20:56:27.6002054Z def main() -> None: 2025-06-01T20:56:27.6002459Z args = parse_args() 2025-06-01T20:56:27.6002727Z 2025-06-01T20:56:27.6002947Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:27.6003342Z 2025-06-01T20:56:27.6003537Z # Check if the PR is opt-out 2025-06-01T20:56:27.6004023Z if args.pr_number: 2025-06-01T20:56:27.6004690Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:27.6005579Z if OPT_OUT_LABEL in labels: 2025-06-01T20:56:27.6006087Z log.info( 2025-06-01T20:56:27.6006774Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:27.6007571Z ) 2025-06-01T20:56:27.6008113Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.6008800Z sys.exit() 2025-06-01T20:56:27.6009055Z 2025-06-01T20:56:27.6009218Z try: 2025-06-01T20:56:27.6009638Z rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:27.6010359Z args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:27.6011110Z ) 2025-06-01T20:56:27.6011313Z 2025-06-01T20:56:27.6011514Z username = get_potential_pr_author( 2025-06-01T20:56:27.6012061Z args.github_token, 2025-06-01T20:56:27.6012536Z args.github_repo, 2025-06-01T20:56:27.6013000Z args.github_actor, 2025-06-01T20:56:27.6013481Z args.github_ref_type, 2025-06-01T20:56:27.6013975Z args.github_branch, 2025-06-01T20:56:27.6014438Z ) 2025-06-01T20:56:27.6014639Z 2025-06-01T20:56:27.6014931Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:27.6015399Z 2025-06-01T20:56:27.6015606Z runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:27.6016159Z rollout_state, 2025-06-01T20:56:27.6016631Z (args.github_issue_owner, username), 2025-06-01T20:56:27.6017184Z args.github_branch, 2025-06-01T20:56:27.6017668Z args.eligible_experiments, 2025-06-01T20:56:27.6018205Z args.opt_out_experiments, 2025-06-01T20:56:27.6018707Z is_canary, 2025-06-01T20:56:27.6019105Z ) 2025-06-01T20:56:27.6019313Z 2025-06-01T20:56:27.6019501Z except Exception as e: 2025-06-01T20:56:27.6019938Z log.error( 2025-06-01T20:56:27.6020617Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:27.6021505Z ) 2025-06-01T20:56:27.6021714Z 2025-06-01T20:56:27.6022045Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.6022565Z 2025-06-01T20:56:27.6022572Z 2025-06-01T20:56:27.6022752Z if __name__ == "__main__": 2025-06-01T20:56:27.6023180Z main() 2025-06-01T20:56:27.6023381Z 2025-06-01T20:56:27.6112384Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:27.6113308Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:27.6164220Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:27.6164714Z env: 2025-06-01T20:56:27.6165322Z GITHUB_TOKEN: *** 2025-06-01T20:56:27.6165745Z ISSUE_NUMBER: 5132 2025-06-01T20:56:27.6166343Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:27.6166877Z ISSUE_OWNER: 2025-06-01T20:56:27.6167266Z CHECK_EXPERIMENTS: 2025-06-01T20:56:27.6167696Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:27.6168133Z PR_NUMBER: 2025-06-01T20:56:27.6168510Z ##[endgroup] 2025-06-01T20:56:28.0116213Z Defaulting to user installation because normal site-packages is not writeable 2025-06-01T20:56:28.3938412Z Collecting urllib3==1.26.18 2025-06-01T20:56:28.4425474Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-06-01T20:56:28.4640654Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.6 MB/s eta 0:00:00 2025-06-01T20:56:28.4902863Z Collecting PyGithub==2.3.0 2025-06-01T20:56:28.4985716Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-06-01T20:56:28.5448664Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-06-01T20:56:28.5519651Z 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-06-01T20:56:28.5572056Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-06-01T20:56:28.5588586Z 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-06-01T20:56:28.5603333Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-06-01T20:56:28.5893920Z Collecting Deprecated (from PyGithub==2.3.0) 2025-06-01T20:56:28.5965461Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-06-01T20:56:28.6198436Z 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-06-01T20:56:28.7370723Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:28.7453538Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-06-01T20:56:28.8709468Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-06-01T20:56:28.8781803Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.4 kB) 2025-06-01T20:56:28.9018689Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:28.9092061Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-06-01T20:56:28.9367597Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-06-01T20:56:28.9485112Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 14.5 MB/s eta 0:00:00 2025-06-01T20:56:28.9559682Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-06-01T20:56:28.9692501Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 33.1 MB/s eta 0:00:00 2025-06-01T20:56:28.9771982Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-06-01T20:56:28.9965778Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 50.8 MB/s eta 0:00:00 2025-06-01T20:56:29.0041090Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-06-01T20:56:29.0152928Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-06-01T20:56:29.0255141Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 64.3 MB/s eta 0:00:00 2025-06-01T20:56:29.0329554Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (89 kB) 2025-06-01T20:56:29.0390546Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 21.9 MB/s eta 0:00:00 2025-06-01T20:56:29.0459479Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-06-01T20:56:29.0507901Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 33.1 MB/s eta 0:00:00 2025-06-01T20:56:29.3404832Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-06-01T20:56:29.8723965Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.2 2025-06-01T20:56:29.9498730Z ##[group]Run curr_branch="main" 2025-06-01T20:56:29.9499038Z curr_branch="main" 2025-06-01T20:56:29.9499255Z curr_ref_type="branch" 2025-06-01T20:56:29.9499495Z echo "Current branch is '$curr_branch'" 2025-06-01T20:56:29.9499747Z  2025-06-01T20:56:29.9499939Z python3 runner_determinator.py \ 2025-06-01T20:56:29.9500220Z  --github-token "$GITHUB_TOKEN" \ 2025-06-01T20:56:29.9500492Z  --github-issue "$ISSUE_NUMBER" \ 2025-06-01T20:56:29.9500746Z  --github-branch "$curr_branch" \ 2025-06-01T20:56:29.9501231Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-06-01T20:56:29.9501516Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-06-01T20:56:29.9501793Z  --github-ref-type "$curr_ref_type" \ 2025-06-01T20:56:29.9502069Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-06-01T20:56:29.9502400Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-06-01T20:56:29.9502769Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-06-01T20:56:29.9503066Z  --pr-number "${PR_NUMBER}" 2025-06-01T20:56:29.9555507Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:29.9555732Z env: 2025-06-01T20:56:29.9556290Z GITHUB_TOKEN: *** 2025-06-01T20:56:29.9556481Z ISSUE_NUMBER: 5132 2025-06-01T20:56:29.9556684Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:29.9556917Z ISSUE_OWNER: 2025-06-01T20:56:29.9557091Z CHECK_EXPERIMENTS: 2025-06-01T20:56:29.9557276Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:29.9557459Z PR_NUMBER: 2025-06-01T20:56:29.9557628Z ##[endgroup] 2025-06-01T20:56:29.9625649Z Current branch is 'main' 2025-06-01T20:56:31.0981625Z INFO : Based on rollout percentage of 30%, enabling experiment lf. 2025-06-01T20:56:31.0982911Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-06-01T20:56:31.0983889Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-06-01T20:56:31.0984559Z INFO : Setting output: label-type='lf.' 2025-06-01T20:56:31.1290499Z Evaluate and set job outputs 2025-06-01T20:56:31.1296729Z Set output 'label-type' 2025-06-01T20:56:31.1298928Z Cleaning up orphan processes