2025-12-04T08:34:36.6448191Z Current runner version: '2.329.0' 2025-12-04T08:34:36.6471786Z ##[group]Runner Image Provisioner 2025-12-04T08:34:36.6472556Z Hosted Compute Agent 2025-12-04T08:34:36.6473177Z Version: 20251124.448 2025-12-04T08:34:36.6473734Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T08:34:36.6474412Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T08:34:36.6475000Z ##[endgroup] 2025-12-04T08:34:36.6475579Z ##[group]Operating System 2025-12-04T08:34:36.6476105Z Ubuntu 2025-12-04T08:34:36.6476617Z 24.04.3 2025-12-04T08:34:36.6477108Z LTS 2025-12-04T08:34:36.6477523Z ##[endgroup] 2025-12-04T08:34:36.6478034Z ##[group]Runner Image 2025-12-04T08:34:36.6478573Z Image: ubuntu-24.04 2025-12-04T08:34:36.6479070Z Version: 20251126.144.1 2025-12-04T08:34:36.6480041Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T08:34:36.6481771Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T08:34:36.6482795Z ##[endgroup] 2025-12-04T08:34:36.6483790Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T08:34:36.6485815Z Contents: read 2025-12-04T08:34:36.6486413Z Metadata: read 2025-12-04T08:34:36.6486916Z ##[endgroup] 2025-12-04T08:34:36.6488793Z Secret source: Actions 2025-12-04T08:34:36.6489550Z Prepare workflow directory 2025-12-04T08:34:36.6989144Z Prepare all required actions 2025-12-04T08:34:36.7044225Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T08:34:36.7048836Z ##[group] Inputs 2025-12-04T08:34:36.7049418Z check_experiments: 2025-12-04T08:34:36.7050065Z opt_out_experiments: 2025-12-04T08:34:36.7050641Z triggering_actor: pytorchmergebot 2025-12-04T08:34:36.7051230Z issue_owner: 2025-12-04T08:34:36.7052084Z curr_branch: main 2025-12-04T08:34:36.7052639Z curr_ref_type: branch 2025-12-04T08:34:36.7053248Z issue_number: 5132 2025-12-04T08:34:36.7053885Z ##[endgroup] 2025-12-04T08:34:36.7054470Z Complete job name: get-label-type / runner-determinator 2025-12-04T08:34:37.4317258Z ##[group]Run cat < runner_determinator.py 2025-12-04T08:34:37.4319699Z cat < runner_determinator.py 2025-12-04T08:34:37.4320319Z # flake8: noqa: G004 2025-12-04T08:34:37.4320928Z  2025-12-04T08:34:37.4321862Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:34:37.4322905Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:34:37.4323950Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:34:37.4324616Z  2025-12-04T08:34:37.4325037Z """ 2025-12-04T08:34:37.4325764Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:34:37.4326749Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:34:37.4327912Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:34:37.4328851Z of which runners should be used to run which job. 2025-12-04T08:34:37.4329503Z  2025-12-04T08:34:37.4330219Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:34:37.4331248Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:34:37.4332376Z settings are considered to be empty with only the second part, the user 2025-12-04T08:34:37.4333242Z list, defined. 2025-12-04T08:34:37.4333732Z  2025-12-04T08:34:37.4334387Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:34:37.4335445Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:34:37.4336379Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:34:37.4337082Z  2025-12-04T08:34:37.4337963Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:34:37.4339020Z The user list is also a comma separated list of additional features or 2025-12-04T08:34:37.4339894Z experiments which the user could be opted in to. 2025-12-04T08:34:37.4340505Z  2025-12-04T08:34:37.4341063Z The user list has the following rules: 2025-12-04T08:34:37.4341762Z  2025-12-04T08:34:37.4342596Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:34:37.4343589Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:34:37.4344477Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:34:37.4345103Z  2025-12-04T08:34:37.4345589Z Example config: 2025-12-04T08:34:37.4346218Z  # A list of experiments that can be opted into. 2025-12-04T08:34:37.4346966Z  # This defines the behavior they'll induce when opted into. 2025-12-04T08:34:37.4347760Z  # Expected syntax is: 2025-12-04T08:34:37.4348505Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:34:37.4349576Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:34:37.4350472Z  2025-12-04T08:34:37.4350889Z  experiments: 2025-12-04T08:34:37.4351441Z  lf: 2025-12-04T08:34:37.4352186Z  rollout_percent: 25 2025-12-04T08:34:37.4352805Z  all_branches: false 2025-12-04T08:34:37.4353421Z  default: true 2025-12-04T08:34:37.4353947Z  --- 2025-12-04T08:34:37.4354457Z  2025-12-04T08:34:37.4354864Z  # Opt-ins: 2025-12-04T08:34:37.4355625Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:34:37.4356779Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:34:37.4357725Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:34:37.4358570Z  # Experiments should be from the above list. 2025-12-04T08:34:37.4359238Z  2025-12-04T08:34:37.4359723Z  @User1,-lf,split_build 2025-12-04T08:34:37.4360250Z  @User2,lf 2025-12-04T08:34:37.4360840Z  @User3,split_build 2025-12-04T08:34:37.4361357Z """ 2025-12-04T08:34:37.4362018Z  2025-12-04T08:34:37.4439374Z import json 2025-12-04T08:34:37.4439819Z import logging 2025-12-04T08:34:37.4440250Z import os 2025-12-04T08:34:37.4440677Z import random 2025-12-04T08:34:37.4441092Z import re 2025-12-04T08:34:37.4441474Z import sys 2025-12-04T08:34:37.4442203Z from argparse import ArgumentParser 2025-12-04T08:34:37.4442854Z from collections.abc import Iterable 2025-12-04T08:34:37.4443419Z from functools import cache 2025-12-04T08:34:37.4443929Z from logging import LogRecord 2025-12-04T08:34:37.4444462Z from typing import Any, NamedTuple 2025-12-04T08:34:37.4445050Z from urllib.request import Request, urlopen 2025-12-04T08:34:37.4445583Z  2025-12-04T08:34:37.4445925Z import yaml 2025-12-04T08:34:37.4446356Z from github import Auth, Github 2025-12-04T08:34:37.4446869Z from github.Issue import Issue 2025-12-04T08:34:37.4447346Z  2025-12-04T08:34:37.4447695Z  2025-12-04T08:34:37.4448125Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:34:37.4448843Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:34:37.4449710Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:34:37.4450410Z  2025-12-04T08:34:37.4451138Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:34:37.4451852Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:34:37.4452404Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:34:37.4452987Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:34:37.4453500Z  2025-12-04T08:34:37.4453895Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:34:37.4454395Z  2025-12-04T08:34:37.4454764Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:34:37.4455262Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:34:37.4455718Z  2025-12-04T08:34:37.4456056Z  2025-12-04T08:34:37.4456433Z class Experiment(NamedTuple): 2025-12-04T08:34:37.4456944Z  rollout_perc: float = ( 2025-12-04T08:34:37.4457625Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:34:37.4458296Z  ) 2025-12-04T08:34:37.4458696Z  all_branches: bool = ( 2025-12-04T08:34:37.4459408Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:34:37.4460076Z  ) 2025-12-04T08:34:37.4460453Z  default: bool = ( 2025-12-04T08:34:37.4461067Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T08:34:37.4461806Z  ) 2025-12-04T08:34:37.4462163Z  2025-12-04T08:34:37.4462531Z  # Add more fields as needed 2025-12-04T08:34:37.4463001Z  2025-12-04T08:34:37.4463339Z  2025-12-04T08:34:37.4463753Z class Settings(NamedTuple): 2025-12-04T08:34:37.4464254Z  """ 2025-12-04T08:34:37.4464747Z  Settings for the experiments that can be opted into. 2025-12-04T08:34:37.4465331Z  """ 2025-12-04T08:34:37.4465690Z  2025-12-04T08:34:37.4466087Z  experiments: dict[str, Experiment] = {} 2025-12-04T08:34:37.4466611Z  2025-12-04T08:34:37.4467116Z  2025-12-04T08:34:37.4467533Z class ColorFormatter(logging.Formatter): 2025-12-04T08:34:37.4468182Z  """Color codes the log messages based on the log level""" 2025-12-04T08:34:37.4468769Z  2025-12-04T08:34:37.4469110Z  COLORS = { 2025-12-04T08:34:37.4469543Z  "WARNING": "\033[33m", # Yellow 2025-12-04T08:34:37.4470060Z  "ERROR": "\033[31m", # Red 2025-12-04T08:34:37.4470574Z  "CRITICAL": "\033[31m", # Red 2025-12-04T08:34:37.4471087Z  "INFO": "\033[0m", # Reset 2025-12-04T08:34:37.4471698Z  "DEBUG": "\033[0m", # Reset 2025-12-04T08:34:37.4472185Z  } 2025-12-04T08:34:37.4472535Z  2025-12-04T08:34:37.4472948Z  def format(self, record: LogRecord) -> str: 2025-12-04T08:34:37.4473702Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:34:37.4474479Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:34:37.4475062Z  return super().format(record) 2025-12-04T08:34:37.4475551Z  2025-12-04T08:34:37.4475883Z  2025-12-04T08:34:37.4476261Z handler = logging.StreamHandler() 2025-12-04T08:34:37.4477006Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:34:37.4477707Z  2025-12-04T08:34:37.4478164Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:34:37.4478754Z log.addHandler(handler) 2025-12-04T08:34:37.4479228Z log.setLevel(logging.INFO) 2025-12-04T08:34:37.4479691Z  2025-12-04T08:34:37.4480019Z  2025-12-04T08:34:37.4480482Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:34:37.4481054Z  """ 2025-12-04T08:34:37.4481829Z  Defines outputs of the github action that invokes this script 2025-12-04T08:34:37.4482708Z  """ 2025-12-04T08:34:37.4483103Z  if not GITHUB_OUTPUT: 2025-12-04T08:34:37.4484185Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:34:37.4485262Z  log.warning( 2025-12-04T08:34:37.4486129Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:34:37.4487016Z  ) 2025-12-04T08:34:37.4487474Z  print(f"::set-output name={key}::{value}") 2025-12-04T08:34:37.4488020Z  return 2025-12-04T08:34:37.4488409Z  2025-12-04T08:34:37.4488801Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:34:37.4489378Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:34:37.4489955Z  f.write(f"{key}={value}\n") 2025-12-04T08:34:37.4490446Z  2025-12-04T08:34:37.4490781Z  2025-12-04T08:34:37.4491287Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:34:37.4492245Z  return frozenset( 2025-12-04T08:34:37.4492898Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:34:37.4493561Z  ) 2025-12-04T08:34:37.4493918Z  2025-12-04T08:34:37.4494297Z  2025-12-04T08:34:37.4494653Z def parse_args() -> Any: 2025-12-04T08:34:37.4495245Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:34:37.4496110Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:34:37.4496860Z  parser.add_argument( 2025-12-04T08:34:37.4497337Z  "--github-issue-repo", 2025-12-04T08:34:37.4497834Z  type=str, 2025-12-04T08:34:37.4498272Z  required=False, 2025-12-04T08:34:37.4498887Z  default="pytorch/test-infra", 2025-12-04T08:34:37.4499454Z  help="GitHub repo to get the issue", 2025-12-04T08:34:37.4499972Z  ) 2025-12-04T08:34:37.4500350Z  parser.add_argument( 2025-12-04T08:34:37.4500823Z  "--github-repo", 2025-12-04T08:34:37.4501283Z  type=str, 2025-12-04T08:34:37.4501830Z  required=True, 2025-12-04T08:34:37.4502342Z  help="GitHub repo where CI is running", 2025-12-04T08:34:37.4502858Z  ) 2025-12-04T08:34:37.4503246Z  parser.add_argument( 2025-12-04T08:34:37.4503891Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:34:37.4504540Z  ) 2025-12-04T08:34:37.4504928Z  parser.add_argument( 2025-12-04T08:34:37.4505581Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:34:37.4506257Z  ) 2025-12-04T08:34:37.4506637Z  parser.add_argument( 2025-12-04T08:34:37.4507309Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:34:37.4507980Z  ) 2025-12-04T08:34:37.4508369Z  parser.add_argument( 2025-12-04T08:34:37.4509066Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:34:37.4509760Z  ) 2025-12-04T08:34:37.4510148Z  parser.add_argument( 2025-12-04T08:34:37.4510634Z  "--github-ref-type", 2025-12-04T08:34:37.4511113Z  type=str, 2025-12-04T08:34:37.4511642Z  required=True, 2025-12-04T08:34:37.4512192Z  help="Current GitHub ref type, branch or tag", 2025-12-04T08:34:37.4512758Z  ) 2025-12-04T08:34:37.4513146Z  parser.add_argument( 2025-12-04T08:34:37.4513769Z  "--eligible-experiments", 2025-12-04T08:34:37.4514318Z  type=_str_comma_separated_to_set, 2025-12-04T08:34:37.4514839Z  required=False, 2025-12-04T08:34:37.4515297Z  default="", 2025-12-04T08:34:37.4516160Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:34:37.4517056Z  ) 2025-12-04T08:34:37.4517437Z  parser.add_argument( 2025-12-04T08:34:37.4517925Z  "--opt-out-experiments", 2025-12-04T08:34:37.4518470Z  type=_str_comma_separated_to_set, 2025-12-04T08:34:37.4519010Z  required=False, 2025-12-04T08:34:37.4519466Z  default="", 2025-12-04T08:34:37.4519894Z  help=( 2025-12-04T08:34:37.4520593Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:34:37.4521931Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:34:37.4522775Z  ), 2025-12-04T08:34:37.4523159Z  ) 2025-12-04T08:34:37.4523535Z  parser.add_argument( 2025-12-04T08:34:37.4524003Z  "--pr-number", 2025-12-04T08:34:37.4524454Z  type=str, 2025-12-04T08:34:37.4524891Z  required=False, 2025-12-04T08:34:37.4525343Z  default="", 2025-12-04T08:34:37.4525866Z  help="the optional PR number where this is run", 2025-12-04T08:34:37.4526415Z  ) 2025-12-04T08:34:37.4526768Z  2025-12-04T08:34:37.4527146Z  return parser.parse_args() 2025-12-04T08:34:37.4527623Z  2025-12-04T08:34:37.4527957Z  2025-12-04T08:34:37.4528551Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:34:37.4529449Z  auth = Auth.Token(github_token) 2025-12-04T08:34:37.4529971Z  return Github(auth=auth) 2025-12-04T08:34:37.4530429Z  2025-12-04T08:34:37.4530761Z  2025-12-04T08:34:37.4531402Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:34:37.4532304Z  repo = gh.get_repo(repo) 2025-12-04T08:34:37.4532838Z  return repo.get_issue(number=issue_num) 2025-12-04T08:34:37.4533350Z  2025-12-04T08:34:37.4533678Z  2025-12-04T08:34:37.4534039Z def get_potential_pr_author( 2025-12-04T08:34:37.4534728Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:34:37.4535398Z ) -> str: 2025-12-04T08:34:37.4535948Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:34:37.4536750Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:34:37.4537514Z  # embedded in the tag name: ciflow// 2025-12-04T08:34:37.4538080Z  2025-12-04T08:34:37.4538462Z  gh = get_gh_client(github_token) 2025-12-04T08:34:37.4538951Z  2025-12-04T08:34:37.4539418Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:34:37.4540056Z  split_tag = ref_name.split("/") 2025-12-04T08:34:37.4540551Z  if ( 2025-12-04T08:34:37.4540967Z  len(split_tag) == 3 2025-12-04T08:34:37.4541476Z  and split_tag[0] == "ciflow" 2025-12-04T08:34:37.4542129Z  and split_tag[2].isnumeric() 2025-12-04T08:34:37.4542624Z  ): 2025-12-04T08:34:37.4543041Z  pr_number = split_tag[2] 2025-12-04T08:34:37.4543534Z  try: 2025-12-04T08:34:37.4544002Z  repository = gh.get_repo(repo) 2025-12-04T08:34:37.4544767Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:34:37.4545377Z  except Exception as e: 2025-12-04T08:34:37.4545941Z  raise Exception( # noqa: TRY002 2025-12-04T08:34:37.4546629Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:34:37.4547277Z  ) from e 2025-12-04T08:34:37.4547862Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:34:37.4548571Z  # In all other cases, return the original input username 2025-12-04T08:34:37.4549173Z  return username 2025-12-04T08:34:37.4549593Z  2025-12-04T08:34:37.4549928Z  2025-12-04T08:34:37.4550361Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:34:37.4550889Z  """ 2025-12-04T08:34:37.4551675Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:34:37.4552458Z  """ 2025-12-04T08:34:37.4553026Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:34:37.4553675Z  2025-12-04T08:34:37.4554016Z  2025-12-04T08:34:37.4554407Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:34:37.4554907Z  try: 2025-12-04T08:34:37.4555313Z  data = yaml.safe_load(yaml_text) 2025-12-04T08:34:37.4555825Z  return data 2025-12-04T08:34:37.4556278Z  except yaml.YAMLError: 2025-12-04T08:34:37.4556804Z  log.exception("Error loading YAML") 2025-12-04T08:34:37.4557321Z  raise 2025-12-04T08:34:37.4557715Z  2025-12-04T08:34:37.4558053Z  2025-12-04T08:34:37.4558660Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:34:37.4559397Z  """ 2025-12-04T08:34:37.4560162Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:34:37.4560892Z  2025-12-04T08:34:37.4561433Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:34:37.4562426Z  and the text below is the list of opted in users. 2025-12-04T08:34:37.4562979Z  2025-12-04T08:34:37.4563542Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:34:37.4564240Z  """ 2025-12-04T08:34:37.4564710Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T08:34:37.4565309Z  if len(rollout_state_parts) >= 2: 2025-12-04T08:34:37.4565936Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:34:37.4566513Z  else: 2025-12-04T08:34:37.4567002Z  return "", rollout_state 2025-12-04T08:34:37.4567628Z  2025-12-04T08:34:37.4567964Z  2025-12-04T08:34:37.4568359Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:34:37.4568860Z  """ 2025-12-04T08:34:37.4569396Z  Dictionary of users with a list of features they have opted into 2025-12-04T08:34:37.4570021Z  """ 2025-12-04T08:34:37.4570377Z  2025-12-04T08:34:37.4570697Z  2025-12-04T08:34:37.4571228Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:34:37.4572217Z  """ 2025-12-04T08:34:37.4572928Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T08:34:37.4573721Z  2025-12-04T08:34:37.4574499Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:34:37.4575474Z  - Example line: "@User1,lf,split_build" 2025-12-04T08:34:37.4576299Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:34:37.4576912Z  2025-12-04T08:34:37.4577237Z  2025-12-04T08:34:37.4577563Z  """ 2025-12-04T08:34:37.4577941Z  optins = UserOptins() 2025-12-04T08:34:37.4578457Z  for user in user_optin_text.split("\n"): 2025-12-04T08:34:37.4579024Z  user = user.strip("\r\n\t -") 2025-12-04T08:34:37.4579595Z  if not user or not user.startswith("@"): 2025-12-04T08:34:37.4580155Z  # Not a valid user. Skip 2025-12-04T08:34:37.4580652Z  continue 2025-12-04T08:34:37.4581062Z  2025-12-04T08:34:37.4581400Z  if user: 2025-12-04T08:34:37.4582342Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T08:34:37.4583069Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:34:37.4583690Z  2025-12-04T08:34:37.4584039Z  return optins 2025-12-04T08:34:37.4584439Z  2025-12-04T08:34:37.4584760Z  2025-12-04T08:34:37.4585250Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:34:37.4585855Z  """ 2025-12-04T08:34:37.4586271Z  Check if the experiment name is valid. 2025-12-04T08:34:37.4586787Z  A valid name: 2025-12-04T08:34:37.4587451Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:34:37.4588373Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:34:37.4589094Z  - Cannot contain spaces 2025-12-04T08:34:37.4589562Z  """ 2025-12-04T08:34:37.4589905Z  2025-12-04T08:34:37.4590353Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:34:37.4591054Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:34:37.4591892Z  2025-12-04T08:34:37.4592233Z  if valid: 2025-12-04T08:34:37.4592774Z  return True 2025-12-04T08:34:37.4593291Z  2025-12-04T08:34:37.4593633Z  log.error( 2025-12-04T08:34:37.4595048Z  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-12-04T08:34:37.4596506Z  ) 2025-12-04T08:34:37.4596867Z  return False 2025-12-04T08:34:37.4597261Z  2025-12-04T08:34:37.4597590Z  2025-12-04T08:34:37.4598096Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:34:37.4598720Z  """ 2025-12-04T08:34:37.4599318Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:34:37.4600007Z  """ 2025-12-04T08:34:37.4600366Z  try: 2025-12-04T08:34:37.4600737Z  if settings_text: 2025-12-04T08:34:37.4601477Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:34:37.4602417Z  # for easy reading 2025-12-04T08:34:37.4603236Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:34:37.4604116Z  # the backtick character in shell commands. 2025-12-04T08:34:37.4604727Z  backtick = chr(96) # backtick character 2025-12-04T08:34:37.4605402Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:34:37.4606068Z  settings = load_yaml(settings_text) 2025-12-04T08:34:37.4606569Z  2025-12-04T08:34:37.4607159Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:34:37.4608014Z  experiments = {} 2025-12-04T08:34:37.4608473Z  2025-12-04T08:34:37.4609026Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:34:37.4609788Z  if not is_valid_experiment_name(exp_name): 2025-12-04T08:34:37.4610857Z  # 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-12-04T08:34:37.4611975Z  continue 2025-12-04T08:34:37.4612425Z  2025-12-04T08:34:37.4612787Z  valid_settings = {} 2025-12-04T08:34:37.4613318Z  for setting in exp_settings: 2025-12-04T08:34:37.4613891Z  if setting not in Experiment._fields: 2025-12-04T08:34:37.4614452Z  log.warning( 2025-12-04T08:34:37.4615177Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:34:37.4615867Z  ) 2025-12-04T08:34:37.4616313Z  else: 2025-12-04T08:34:37.4616856Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T08:34:37.4617413Z  2025-12-04T08:34:37.4617880Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:34:37.4618521Z  return Settings(experiments) 2025-12-04T08:34:37.4619009Z  2025-12-04T08:34:37.4619360Z  except Exception: 2025-12-04T08:34:37.4619878Z  log.exception("Failed to parse settings") 2025-12-04T08:34:37.4620403Z  2025-12-04T08:34:37.4620753Z  return Settings() 2025-12-04T08:34:37.4621170Z  2025-12-04T08:34:37.4621498Z  2025-12-04T08:34:37.4622624Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:34:37.4623540Z  """ 2025-12-04T08:34:37.4624229Z  Parse settings, if any, from the rollout state. 2025-12-04T08:34:37.4625058Z  2025-12-04T08:34:37.4625937Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:34:37.4627211Z  and the text below is the list of opted in users. 2025-12-04T08:34:37.4628124Z  2025-12-04T08:34:37.4629096Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:34:37.4630304Z  """ 2025-12-04T08:34:37.4631251Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:34:37.4632708Z  return parse_settings_from_text(settings_text) 2025-12-04T08:34:37.4633608Z  2025-12-04T08:34:37.4634138Z  2025-12-04T08:34:37.4634880Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:34:37.4635811Z  """ 2025-12-04T08:34:37.4636480Z  Parse users from the rollout state. 2025-12-04T08:34:37.4637309Z  2025-12-04T08:34:37.4637842Z  """ 2025-12-04T08:34:37.4638752Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:34:37.4639995Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T08:34:37.4640893Z  2025-12-04T08:34:37.4641426Z  2025-12-04T08:34:37.4643054Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:34:37.4644216Z  """ 2025-12-04T08:34:37.4644904Z  Check if a user is opted into an experiment 2025-12-04T08:34:37.4645733Z  """ 2025-12-04T08:34:37.4646517Z  return experiment_name in user_optins.get(user, []) 2025-12-04T08:34:37.4647471Z  2025-12-04T08:34:37.4648197Z  2025-12-04T08:34:37.4649231Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:34:37.4650474Z  """ 2025-12-04T08:34:37.4651262Z  Check if a user explicitly opted out of an experiment 2025-12-04T08:34:37.4652381Z  """ 2025-12-04T08:34:37.4653256Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:34:37.4654422Z  experiment_optout = "-" + experiment_name 2025-12-04T08:34:37.4655498Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:34:37.4656522Z  return False 2025-12-04T08:34:37.4657214Z  2025-12-04T08:34:37.4657978Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:34:37.4658976Z  log.warning( 2025-12-04T08:34:37.4660354Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:34:37.4661953Z  ) 2025-12-04T08:34:37.4662550Z  2025-12-04T08:34:37.4663112Z  return True 2025-12-04T08:34:37.4663764Z  2025-12-04T08:34:37.4664307Z  2025-12-04T08:34:37.4664829Z def get_runner_prefix( 2025-12-04T08:34:37.4665529Z  rollout_state: str, 2025-12-04T08:34:37.4666271Z  workflow_requestors: Iterable[str], 2025-12-04T08:34:37.4667089Z  branch: str, 2025-12-04T08:34:37.4667961Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:34:37.4669106Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:34:37.4670067Z  is_canary: bool = False, 2025-12-04T08:34:37.4670813Z ) -> str: 2025-12-04T08:34:37.4671524Z  settings = parse_settings(rollout_state) 2025-12-04T08:34:37.4672612Z  user_optins = parse_users(rollout_state) 2025-12-04T08:34:37.4673465Z  2025-12-04T08:34:37.4674211Z  fleet_prefix = "" 2025-12-04T08:34:37.4674939Z  prefixes = [] 2025-12-04T08:34:37.4676022Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:34:37.4677586Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:34:37.4678755Z  log.info( 2025-12-04T08:34:37.4679922Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:34:37.4681153Z  ) 2025-12-04T08:34:37.4681968Z  continue 2025-12-04T08:34:37.4682644Z  2025-12-04T08:34:37.4683244Z  if opt_out_experiments: 2025-12-04T08:34:37.4684164Z  if experiment_name in opt_out_experiments: 2025-12-04T08:34:37.4685227Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:34:37.4686176Z  log.info( 2025-12-04T08:34:37.4687622Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:34:37.4689239Z  ) 2025-12-04T08:34:37.4689922Z  continue 2025-12-04T08:34:37.4690644Z  2025-12-04T08:34:37.4691237Z  if eligible_experiments: 2025-12-04T08:34:37.4692327Z  if experiment_name not in eligible_experiments: 2025-12-04T08:34:37.4693381Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T08:34:37.4694282Z  log.info( 2025-12-04T08:34:37.4695611Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:34:37.4696980Z  ) 2025-12-04T08:34:37.4697655Z  continue 2025-12-04T08:34:37.4698654Z  elif not experiment_settings.default: 2025-12-04T08:34:37.4699517Z  log.info( 2025-12-04T08:34:37.4700651Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:34:37.4702042Z  ) 2025-12-04T08:34:37.4702720Z  continue 2025-12-04T08:34:37.4703410Z  2025-12-04T08:34:37.4704200Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:34:37.4705172Z  opted_out_users = [ 2025-12-04T08:34:37.4705671Z  requestor 2025-12-04T08:34:37.4706177Z  for requestor in workflow_requestors 2025-12-04T08:34:37.4707091Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:34:37.4708141Z  ] 2025-12-04T08:34:37.4708732Z  2025-12-04T08:34:37.4709324Z  if opted_out_users: 2025-12-04T08:34:37.4710095Z  log.info( 2025-12-04T08:34:37.4711162Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:34:37.4712471Z  ) 2025-12-04T08:34:37.4713111Z  continue 2025-12-04T08:34:37.4713790Z  2025-12-04T08:34:37.4714544Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:34:37.4715547Z  opted_in_users = [ 2025-12-04T08:34:37.4716307Z  requestor 2025-12-04T08:34:37.4717110Z  for requestor in workflow_requestors 2025-12-04T08:34:37.4718122Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:34:37.4718759Z  ] 2025-12-04T08:34:37.4719148Z  2025-12-04T08:34:37.4719734Z  enabled = False 2025-12-04T08:34:37.4720506Z  if opted_in_users: 2025-12-04T08:34:37.4721467Z  log.info( 2025-12-04T08:34:37.4722691Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:34:37.4723806Z  ) 2025-12-04T08:34:37.4724472Z  enabled = True 2025-12-04T08:34:37.4725205Z  2025-12-04T08:34:37.4725865Z  elif experiment_settings.rollout_perc: 2025-12-04T08:34:37.4727237Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:34:37.4728823Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:34:37.4729489Z  log.info( 2025-12-04T08:34:37.4730362Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:34:37.4731241Z  ) 2025-12-04T08:34:37.4731878Z  enabled = True 2025-12-04T08:34:37.4732354Z  2025-12-04T08:34:37.4732700Z  if enabled: 2025-12-04T08:34:37.4733154Z  label = experiment_name 2025-12-04T08:34:37.4733728Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:34:37.4734534Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:34:37.4735384Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T08:34:37.4736136Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:34:37.4736798Z  if is_canary: 2025-12-04T08:34:37.4737324Z  label += CANARY_FLEET_SUFFIX 2025-12-04T08:34:37.4737871Z  fleet_prefix = label 2025-12-04T08:34:37.4738366Z  else: 2025-12-04T08:34:37.4738981Z  prefixes.append(label) 2025-12-04T08:34:37.4739472Z  2025-12-04T08:34:37.4739830Z  if len(prefixes) > 1: 2025-12-04T08:34:37.4740288Z  log.error( 2025-12-04T08:34:37.4741320Z  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-12-04T08:34:37.4742489Z  ) 2025-12-04T08:34:37.4742890Z  prefixes = prefixes[:1] 2025-12-04T08:34:37.4743355Z  2025-12-04T08:34:37.4743705Z  # Fleet always comes first 2025-12-04T08:34:37.4744187Z  if fleet_prefix: 2025-12-04T08:34:37.4744655Z  prefixes.insert(0, fleet_prefix) 2025-12-04T08:34:37.4745153Z  2025-12-04T08:34:37.4745590Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:34:37.4746146Z  2025-12-04T08:34:37.4746480Z  2025-12-04T08:34:37.4747098Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:34:37.4747847Z  """ 2025-12-04T08:34:37.4748435Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:34:37.4749117Z  2025-12-04T08:34:37.4749676Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:34:37.4750356Z  """ 2025-12-04T08:34:37.4750760Z  gh = get_gh_client(github_token) 2025-12-04T08:34:37.4751305Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T08:34:37.4752057Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:34:37.4752628Z  2025-12-04T08:34:37.4752960Z  2025-12-04T08:34:37.4753551Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:34:37.4754416Z  for _ in range(num_retries): 2025-12-04T08:34:37.4754894Z  try: 2025-12-04T08:34:37.4755332Z  req = Request(url=url, headers=headers) 2025-12-04T08:34:37.4755994Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:34:37.4756627Z  return json.loads(content) 2025-12-04T08:34:37.4757151Z  except Exception as e: 2025-12-04T08:34:37.4757718Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T08:34:37.4758256Z  2025-12-04T08:34:37.4758820Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:34:37.4759513Z  return {} 2025-12-04T08:34:37.4759902Z  2025-12-04T08:34:37.4760224Z  2025-12-04T08:34:37.4760554Z @cache 2025-12-04T08:34:37.4761184Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:34:37.4762024Z  """ 2025-12-04T08:34:37.4762429Z  Dynamically get PR information 2025-12-04T08:34:37.4762920Z  """ 2025-12-04T08:34:37.4763430Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:34:37.4764040Z  headers = { 2025-12-04T08:34:37.4764559Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T08:34:37.4765161Z  "Authorization": f"token {github_token}", 2025-12-04T08:34:37.4765676Z  } 2025-12-04T08:34:37.4766113Z  json_response: dict[str, Any] = download_json( 2025-12-04T08:34:37.4766714Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:34:37.4767244Z  headers=headers, 2025-12-04T08:34:37.4767685Z  ) 2025-12-04T08:34:37.4768030Z  2025-12-04T08:34:37.4768378Z  if not json_response: 2025-12-04T08:34:37.4768969Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:34:37.4769704Z  return {} 2025-12-04T08:34:37.4770107Z  2025-12-04T08:34:37.4770456Z  return json_response 2025-12-04T08:34:37.4770892Z  2025-12-04T08:34:37.4771203Z  2025-12-04T08:34:37.4771883Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:34:37.4772584Z  """ 2025-12-04T08:34:37.4773113Z  Dynamically get the latest list of labels from the pull request 2025-12-04T08:34:37.4773733Z  """ 2025-12-04T08:34:37.4774210Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:34:37.4774795Z  return { 2025-12-04T08:34:37.4775380Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:34:37.4776041Z  } 2025-12-04T08:34:37.4776388Z  2025-12-04T08:34:37.4776708Z  2025-12-04T08:34:37.4777059Z def main() -> None: 2025-12-04T08:34:37.4777497Z  args = parse_args() 2025-12-04T08:34:37.4777936Z  2025-12-04T08:34:37.4778344Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:34:37.4778863Z  2025-12-04T08:34:37.4779224Z  # Check if the PR is opt-out 2025-12-04T08:34:37.4779724Z  if args.pr_number: 2025-12-04T08:34:37.4780397Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:34:37.4781124Z  if OPT_OUT_LABEL in labels: 2025-12-04T08:34:37.4781718Z  log.info( 2025-12-04T08:34:37.4782429Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:34:37.4783162Z  ) 2025-12-04T08:34:37.4783734Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:34:37.4784392Z  sys.exit() 2025-12-04T08:34:37.4784933Z  2025-12-04T08:34:37.4785260Z  try: 2025-12-04T08:34:37.4785721Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T08:34:37.4786414Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:34:37.4787035Z  ) 2025-12-04T08:34:37.4787402Z  2025-12-04T08:34:37.4787801Z  username = get_potential_pr_author( 2025-12-04T08:34:37.4788341Z  args.github_token, 2025-12-04T08:34:37.4788834Z  args.github_repo, 2025-12-04T08:34:37.4789321Z  args.github_actor, 2025-12-04T08:34:37.4789822Z  args.github_ref_type, 2025-12-04T08:34:37.4790328Z  args.github_branch, 2025-12-04T08:34:37.4790785Z  ) 2025-12-04T08:34:37.4791148Z  2025-12-04T08:34:37.4791714Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:34:37.4792302Z  2025-12-04T08:34:37.4792711Z  runner_label_prefix = get_runner_prefix( 2025-12-04T08:34:37.4793242Z  rollout_state, 2025-12-04T08:34:37.4793755Z  (args.github_issue_owner, username), 2025-12-04T08:34:37.4794306Z  args.github_branch, 2025-12-04T08:34:37.4794833Z  args.eligible_experiments, 2025-12-04T08:34:37.4795370Z  args.opt_out_experiments, 2025-12-04T08:34:37.4795873Z  is_canary, 2025-12-04T08:34:37.4796300Z  ) 2025-12-04T08:34:37.4796655Z  2025-12-04T08:34:37.4797013Z  except Exception as e: 2025-12-04T08:34:37.4797474Z  log.error( 2025-12-04T08:34:37.4798160Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:34:37.4798877Z  ) 2025-12-04T08:34:37.4799377Z  2025-12-04T08:34:37.4799886Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:34:37.4800523Z  2025-12-04T08:34:37.4800849Z  2025-12-04T08:34:37.4801196Z if __name__ == "__main__": 2025-12-04T08:34:37.4801739Z  main() 2025-12-04T08:34:37.4802105Z  2025-12-04T08:34:37.4802448Z EOF 2025-12-04T08:34:37.4802790Z  2025-12-04T08:34:37.4803150Z cat runner_determinator.py 2025-12-04T08:34:37.5395425Z shell: /usr/bin/bash -e {0} 2025-12-04T08:34:37.5396219Z env: 2025-12-04T08:34:37.5396884Z GITHUB_TOKEN: *** 2025-12-04T08:34:37.5397321Z ISSUE_NUMBER: 5132 2025-12-04T08:34:37.5397782Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:34:37.5398306Z ISSUE_OWNER: 2025-12-04T08:34:37.5398727Z CHECK_EXPERIMENTS: 2025-12-04T08:34:37.5399165Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:34:37.5399608Z PR_NUMBER: 2025-12-04T08:34:37.5400011Z ##[endgroup] 2025-12-04T08:34:37.5605085Z # flake8: noqa: G004 2025-12-04T08:34:37.5605412Z 2025-12-04T08:34:37.5605823Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:34:37.5606703Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:34:37.5607452Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:34:37.5607870Z 2025-12-04T08:34:37.5608023Z """ 2025-12-04T08:34:37.5608568Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:34:37.5609398Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:34:37.5610254Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:34:37.5611024Z of which runners should be used to run which job. 2025-12-04T08:34:37.5611400Z 2025-12-04T08:34:37.5612077Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:34:37.5613121Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:34:37.5613947Z settings are considered to be empty with only the second part, the user 2025-12-04T08:34:37.5614594Z list, defined. 2025-12-04T08:34:37.5614813Z 2025-12-04T08:34:37.5615155Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:34:37.5615997Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:34:37.5616770Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:34:37.5617192Z 2025-12-04T08:34:37.5617539Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:34:37.5618341Z The user list is also a comma separated list of additional features or 2025-12-04T08:34:37.5619027Z experiments which the user could be opted in to. 2025-12-04T08:34:37.5619411Z 2025-12-04T08:34:37.5619598Z The user list has the following rules: 2025-12-04T08:34:37.5619939Z 2025-12-04T08:34:37.5620238Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:34:37.5621046Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:34:37.5622031Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:34:37.5622413Z 2025-12-04T08:34:37.5622576Z Example config: 2025-12-04T08:34:37.5623001Z # A list of experiments that can be opted into. 2025-12-04T08:34:37.5623625Z # This defines the behavior they'll induce when opted into. 2025-12-04T08:34:37.5624209Z # Expected syntax is: 2025-12-04T08:34:37.5624807Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:34:37.5625714Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:34:37.5626289Z 2025-12-04T08:34:37.5626446Z experiments: 2025-12-04T08:34:37.5626809Z lf: 2025-12-04T08:34:37.5627145Z rollout_percent: 25 2025-12-04T08:34:37.5627570Z all_branches: false 2025-12-04T08:34:37.5628124Z default: true 2025-12-04T08:34:37.5628505Z --- 2025-12-04T08:34:37.5628697Z 2025-12-04T08:34:37.5628848Z # Opt-ins: 2025-12-04T08:34:37.5629420Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:34:37.5630223Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:34:37.5630943Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:34:37.5631710Z # Experiments should be from the above list. 2025-12-04T08:34:37.5632172Z 2025-12-04T08:34:37.5632346Z @User1,-lf,split_build 2025-12-04T08:34:37.5632758Z @User2,lf 2025-12-04T08:34:37.5633111Z @User3,split_build 2025-12-04T08:34:37.5633486Z """ 2025-12-04T08:34:37.5633664Z 2025-12-04T08:34:37.5633824Z import json 2025-12-04T08:34:37.5634170Z import logging 2025-12-04T08:34:37.5634518Z import os 2025-12-04T08:34:37.5634858Z import random 2025-12-04T08:34:37.5635205Z import re 2025-12-04T08:34:37.5635545Z import sys 2025-12-04T08:34:37.5635924Z from argparse import ArgumentParser 2025-12-04T08:34:37.5636414Z from collections.abc import Iterable 2025-12-04T08:34:37.5636904Z from functools import cache 2025-12-04T08:34:37.5637338Z from logging import LogRecord 2025-12-04T08:34:37.5637803Z from typing import Any, NamedTuple 2025-12-04T08:34:37.5638295Z from urllib.request import Request, urlopen 2025-12-04T08:34:37.5638646Z 2025-12-04T08:34:37.5638797Z import yaml 2025-12-04T08:34:37.5639155Z from github import Auth, Github 2025-12-04T08:34:37.5639611Z from github.Issue import Issue 2025-12-04T08:34:37.5639895Z 2025-12-04T08:34:37.5639900Z 2025-12-04T08:34:37.5640105Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:34:37.5640741Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:34:37.5641716Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:34:37.5642315Z 2025-12-04T08:34:37.5642531Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:34:37.5643209Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:34:37.5643739Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:34:37.5644263Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:34:37.5644592Z 2025-12-04T08:34:37.5644776Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:34:37.5645089Z 2025-12-04T08:34:37.5645260Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:34:37.5645687Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:34:37.5645953Z 2025-12-04T08:34:37.5645960Z 2025-12-04T08:34:37.5646133Z class Experiment(NamedTuple): 2025-12-04T08:34:37.5646576Z rollout_perc: float = ( 2025-12-04T08:34:37.5647153Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:34:37.5647781Z ) 2025-12-04T08:34:37.5648119Z all_branches: bool = ( 2025-12-04T08:34:37.5648695Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:34:37.5649322Z ) 2025-12-04T08:34:37.5649650Z default: bool = ( 2025-12-04T08:34:37.5650175Z True # If True, the experiment is enabled by default for all queries 2025-12-04T08:34:37.5650762Z ) 2025-12-04T08:34:37.5650944Z 2025-12-04T08:34:37.5651114Z # Add more fields as needed 2025-12-04T08:34:37.5651392Z 2025-12-04T08:34:37.5651398Z 2025-12-04T08:34:37.5651780Z class Settings(NamedTuple): 2025-12-04T08:34:37.5652200Z """ 2025-12-04T08:34:37.5652626Z Settings for the experiments that can be opted into. 2025-12-04T08:34:37.5653149Z """ 2025-12-04T08:34:37.5653329Z 2025-12-04T08:34:37.5653525Z experiments: dict[str, Experiment] = {} 2025-12-04T08:34:37.5653868Z 2025-12-04T08:34:37.5653875Z 2025-12-04T08:34:37.5654066Z class ColorFormatter(logging.Formatter): 2025-12-04T08:34:37.5654653Z """Color codes the log messages based on the log level""" 2025-12-04T08:34:37.5655057Z 2025-12-04T08:34:37.5655212Z COLORS = { 2025-12-04T08:34:37.5655578Z "WARNING": "\033[33m", # Yellow 2025-12-04T08:34:37.5656206Z "ERROR": "\033[31m", # Red 2025-12-04T08:34:37.5656666Z "CRITICAL": "\033[31m", # Red 2025-12-04T08:34:37.5657131Z "INFO": "\033[0m", # Reset 2025-12-04T08:34:37.5657576Z "DEBUG": "\033[0m", # Reset 2025-12-04T08:34:37.5658010Z } 2025-12-04T08:34:37.5658188Z 2025-12-04T08:34:37.5658384Z def format(self, record: LogRecord) -> str: 2025-12-04T08:34:37.5659083Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:34:37.5659805Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:34:37.5660341Z return super().format(record) 2025-12-04T08:34:37.5660652Z 2025-12-04T08:34:37.5660659Z 2025-12-04T08:34:37.5660848Z handler = logging.StreamHandler() 2025-12-04T08:34:37.5661502Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:34:37.5662230Z 2025-12-04T08:34:37.5662456Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:34:37.5662994Z log.addHandler(handler) 2025-12-04T08:34:37.5663418Z log.setLevel(logging.INFO) 2025-12-04T08:34:37.5663679Z 2025-12-04T08:34:37.5663686Z 2025-12-04T08:34:37.5663918Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:34:37.5664437Z """ 2025-12-04T08:34:37.5664898Z Defines outputs of the github action that invokes this script 2025-12-04T08:34:37.5665483Z """ 2025-12-04T08:34:37.5665825Z if not GITHUB_OUTPUT: 2025-12-04T08:34:37.5666811Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:34:37.5667848Z log.warning( 2025-12-04T08:34:37.5668636Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:34:37.5669484Z ) 2025-12-04T08:34:37.5679040Z print(f"::set-output name={key}::{value}") 2025-12-04T08:34:37.5679612Z return 2025-12-04T08:34:37.5679836Z 2025-12-04T08:34:37.5680218Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:34:37.5680773Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:34:37.5681309Z f.write(f"{key}={value}\n") 2025-12-04T08:34:37.5681901Z 2025-12-04T08:34:37.5681908Z 2025-12-04T08:34:37.5682244Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:34:37.5682843Z return frozenset( 2025-12-04T08:34:37.5683419Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:34:37.5684061Z ) 2025-12-04T08:34:37.5684247Z 2025-12-04T08:34:37.5684253Z 2025-12-04T08:34:37.5684421Z def parse_args() -> Any: 2025-12-04T08:34:37.5684927Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:34:37.5685730Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:34:37.5686441Z parser.add_argument( 2025-12-04T08:34:37.5686854Z "--github-issue-repo", 2025-12-04T08:34:37.5687278Z type=str, 2025-12-04T08:34:37.5687648Z required=False, 2025-12-04T08:34:37.5688071Z default="pytorch/test-infra", 2025-12-04T08:34:37.5688568Z help="GitHub repo to get the issue", 2025-12-04T08:34:37.5689042Z ) 2025-12-04T08:34:37.5689376Z parser.add_argument( 2025-12-04T08:34:37.5689785Z "--github-repo", 2025-12-04T08:34:37.5690171Z type=str, 2025-12-04T08:34:37.5690534Z required=True, 2025-12-04T08:34:37.5690958Z help="GitHub repo where CI is running", 2025-12-04T08:34:37.5691453Z ) 2025-12-04T08:34:37.5691980Z parser.add_argument( 2025-12-04T08:34:37.5692546Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:34:37.5693169Z ) 2025-12-04T08:34:37.5693503Z parser.add_argument( 2025-12-04T08:34:37.5694094Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:34:37.5694715Z ) 2025-12-04T08:34:37.5695052Z parser.add_argument( 2025-12-04T08:34:37.5695792Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:34:37.5696432Z ) 2025-12-04T08:34:37.5696768Z parser.add_argument( 2025-12-04T08:34:37.5697363Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:34:37.5698019Z ) 2025-12-04T08:34:37.5698349Z parser.add_argument( 2025-12-04T08:34:37.5698769Z "--github-ref-type", 2025-12-04T08:34:37.5699186Z type=str, 2025-12-04T08:34:37.5699559Z required=True, 2025-12-04T08:34:37.5700006Z help="Current GitHub ref type, branch or tag", 2025-12-04T08:34:37.5700512Z ) 2025-12-04T08:34:37.5700843Z parser.add_argument( 2025-12-04T08:34:37.5701266Z "--eligible-experiments", 2025-12-04T08:34:37.5701937Z type=_str_comma_separated_to_set, 2025-12-04T08:34:37.5702423Z required=False, 2025-12-04T08:34:37.5702816Z default="", 2025-12-04T08:34:37.5753266Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:34:37.5754366Z ) 2025-12-04T08:34:37.5754758Z parser.add_argument( 2025-12-04T08:34:37.5755188Z "--opt-out-experiments", 2025-12-04T08:34:37.5755676Z type=_str_comma_separated_to_set, 2025-12-04T08:34:37.5756151Z required=False, 2025-12-04T08:34:37.5756539Z default="", 2025-12-04T08:34:37.5756888Z help=( 2025-12-04T08:34:37.5757514Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:34:37.5758573Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:34:37.5759336Z ), 2025-12-04T08:34:37.5759653Z ) 2025-12-04T08:34:37.5759986Z parser.add_argument( 2025-12-04T08:34:37.5760396Z "--pr-number", 2025-12-04T08:34:37.5760771Z type=str, 2025-12-04T08:34:37.5761136Z required=False, 2025-12-04T08:34:37.5761519Z default="", 2025-12-04T08:34:37.5762270Z help="the optional PR number where this is run", 2025-12-04T08:34:37.5762789Z ) 2025-12-04T08:34:37.5762976Z 2025-12-04T08:34:37.5763147Z return parser.parse_args() 2025-12-04T08:34:37.5763432Z 2025-12-04T08:34:37.5763438Z 2025-12-04T08:34:37.5763849Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:34:37.5764551Z auth = Auth.Token(github_token) 2025-12-04T08:34:37.5765027Z return Github(auth=auth) 2025-12-04T08:34:37.5765303Z 2025-12-04T08:34:37.5765309Z 2025-12-04T08:34:37.5765728Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:34:37.5766471Z repo = gh.get_repo(repo) 2025-12-04T08:34:37.5766935Z return repo.get_issue(number=issue_num) 2025-12-04T08:34:37.5767267Z 2025-12-04T08:34:37.5767273Z 2025-12-04T08:34:37.5767441Z def get_potential_pr_author( 2025-12-04T08:34:37.5768039Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:34:37.5768657Z ) -> str: 2025-12-04T08:34:37.5769125Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:34:37.5769858Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:34:37.5770539Z # embedded in the tag name: ciflow// 2025-12-04T08:34:37.5770918Z 2025-12-04T08:34:37.5771095Z gh = get_gh_client(github_token) 2025-12-04T08:34:37.5771402Z 2025-12-04T08:34:37.5771759Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:34:37.5772337Z split_tag = ref_name.split("/") 2025-12-04T08:34:37.5772796Z if ( 2025-12-04T08:34:37.5773146Z len(split_tag) == 3 2025-12-04T08:34:37.5773578Z and split_tag[0] == "ciflow" 2025-12-04T08:34:37.5774070Z and split_tag[2].isnumeric() 2025-12-04T08:34:37.5774522Z ): 2025-12-04T08:34:37.5774864Z pr_number = split_tag[2] 2025-12-04T08:34:37.5775430Z try: 2025-12-04T08:34:37.5775817Z repository = gh.get_repo(repo) 2025-12-04T08:34:37.5776389Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:34:37.5776935Z except Exception as e: 2025-12-04T08:34:37.5777413Z raise Exception( # noqa: TRY002 2025-12-04T08:34:37.5778029Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:34:37.5778633Z ) from e 2025-12-04T08:34:37.5779124Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:34:37.5779770Z # In all other cases, return the original input username 2025-12-04T08:34:37.5780309Z return username 2025-12-04T08:34:37.5780528Z 2025-12-04T08:34:37.5780534Z 2025-12-04T08:34:37.5780744Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:34:37.5781240Z """ 2025-12-04T08:34:37.5781941Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:34:37.5782703Z """ 2025-12-04T08:34:37.5783199Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:34:37.5783719Z 2025-12-04T08:34:37.5783726Z 2025-12-04T08:34:37.5783905Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:34:37.5784363Z try: 2025-12-04T08:34:37.5784713Z data = yaml.safe_load(yaml_text) 2025-12-04T08:34:37.5785182Z return data 2025-12-04T08:34:37.5785563Z except yaml.YAMLError: 2025-12-04T08:34:37.5786006Z log.exception("Error loading YAML") 2025-12-04T08:34:37.5786468Z raise 2025-12-04T08:34:37.5786672Z 2025-12-04T08:34:37.5786678Z 2025-12-04T08:34:37.5787057Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:34:37.5787734Z """ 2025-12-04T08:34:37.5788294Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:34:37.5788848Z 2025-12-04T08:34:37.5789328Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:34:37.5790027Z and the text below is the list of opted in users. 2025-12-04T08:34:37.5790403Z 2025-12-04T08:34:37.5790743Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:34:37.5791383Z """ 2025-12-04T08:34:37.5792079Z rollout_state_parts = rollout_state.split("---") 2025-12-04T08:34:37.5792651Z if len(rollout_state_parts) >= 2: 2025-12-04T08:34:37.5793202Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:34:37.5793739Z else: 2025-12-04T08:34:37.5794082Z return "", rollout_state 2025-12-04T08:34:37.5794371Z 2025-12-04T08:34:37.5794378Z 2025-12-04T08:34:37.5794559Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:34:37.5795017Z """ 2025-12-04T08:34:37.5795486Z Dictionary of users with a list of features they have opted into 2025-12-04T08:34:37.5796082Z """ 2025-12-04T08:34:37.5796260Z 2025-12-04T08:34:37.5796276Z 2025-12-04T08:34:37.5796589Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:34:37.5797187Z """ 2025-12-04T08:34:37.5797831Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T08:34:37.5798476Z 2025-12-04T08:34:37.5799057Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:34:37.5799967Z - Example line: "@User1,lf,split_build" 2025-12-04T08:34:37.5800604Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:34:37.5801049Z 2025-12-04T08:34:37.5801055Z 2025-12-04T08:34:37.5801204Z """ 2025-12-04T08:34:37.5801539Z optins = UserOptins() 2025-12-04T08:34:37.5802092Z for user in user_optin_text.split("\n"): 2025-12-04T08:34:37.5802597Z user = user.strip("\r\n\t -") 2025-12-04T08:34:37.5803096Z if not user or not user.startswith("@"): 2025-12-04T08:34:37.5803758Z # Not a valid user. Skip 2025-12-04T08:34:37.5804204Z continue 2025-12-04T08:34:37.5804428Z 2025-12-04T08:34:37.5804577Z if user: 2025-12-04T08:34:37.5804967Z usr_name = user.split(",")[0].strip("@") 2025-12-04T08:34:37.5805605Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:34:37.5806061Z 2025-12-04T08:34:37.5806208Z return optins 2025-12-04T08:34:37.5806432Z 2025-12-04T08:34:37.5806438Z 2025-12-04T08:34:37.5806701Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:34:37.5807253Z """ 2025-12-04T08:34:37.5807610Z Check if the experiment name is valid. 2025-12-04T08:34:37.5808084Z A valid name: 2025-12-04T08:34:37.5808655Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:34:37.5809523Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:34:37.5810180Z - Cannot contain spaces 2025-12-04T08:34:37.5810594Z """ 2025-12-04T08:34:37.5810773Z 2025-12-04T08:34:37.5811013Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:34:37.5811759Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:34:37.5812178Z 2025-12-04T08:34:37.5812323Z if valid: 2025-12-04T08:34:37.5812659Z return True 2025-12-04T08:34:37.5812878Z 2025-12-04T08:34:37.5813028Z log.error( 2025-12-04T08:34:37.5814355Z 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-12-04T08:34:37.5815836Z ) 2025-12-04T08:34:37.5816166Z return False 2025-12-04T08:34:37.5816380Z 2025-12-04T08:34:37.5816386Z 2025-12-04T08:34:37.5816658Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:34:37.5817244Z """ 2025-12-04T08:34:37.5817900Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:34:37.5818572Z """ 2025-12-04T08:34:37.5818885Z try: 2025-12-04T08:34:37.5819222Z if settings_text: 2025-12-04T08:34:37.5819887Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:34:37.5820635Z # for easy reading 2025-12-04T08:34:37.5821354Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:34:37.5822276Z # the backtick character in shell commands. 2025-12-04T08:34:37.5822834Z backtick = chr(96) # backtick character 2025-12-04T08:34:37.5823434Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:34:37.5824042Z settings = load_yaml(settings_text) 2025-12-04T08:34:37.5824380Z 2025-12-04T08:34:37.5824760Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:34:37.5825470Z experiments = {} 2025-12-04T08:34:37.5825741Z 2025-12-04T08:34:37.5826098Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:34:37.5826794Z if not is_valid_experiment_name(exp_name): 2025-12-04T08:34:37.5827813Z # 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-12-04T08:34:37.5828771Z continue 2025-12-04T08:34:37.5829031Z 2025-12-04T08:34:37.5829196Z valid_settings = {} 2025-12-04T08:34:37.5829669Z for setting in exp_settings: 2025-12-04T08:34:37.5830189Z if setting not in Experiment._fields: 2025-12-04T08:34:37.5830695Z log.warning( 2025-12-04T08:34:37.5831335Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:34:37.5832438Z ) 2025-12-04T08:34:37.5832836Z else: 2025-12-04T08:34:37.5833309Z valid_settings[setting] = exp_settings[setting] 2025-12-04T08:34:37.5833700Z 2025-12-04T08:34:37.5833958Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:34:37.5834542Z return Settings(experiments) 2025-12-04T08:34:37.5834865Z 2025-12-04T08:34:37.5835025Z except Exception: 2025-12-04T08:34:37.5835455Z log.exception("Failed to parse settings") 2025-12-04T08:34:37.5835816Z 2025-12-04T08:34:37.5835970Z return Settings() 2025-12-04T08:34:37.5836207Z 2025-12-04T08:34:37.5836213Z 2025-12-04T08:34:37.5836440Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:34:37.5836955Z """ 2025-12-04T08:34:37.5837344Z Parse settings, if any, from the rollout state. 2025-12-04T08:34:37.5837713Z 2025-12-04T08:34:37.5838037Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:34:37.5838736Z and the text below is the list of opted in users. 2025-12-04T08:34:37.5839111Z 2025-12-04T08:34:37.5839485Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:34:37.5840157Z """ 2025-12-04T08:34:37.5840659Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:34:37.5841345Z return parse_settings_from_text(settings_text) 2025-12-04T08:34:37.5842144Z 2025-12-04T08:34:37.5842152Z 2025-12-04T08:34:37.5842386Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:34:37.5842890Z """ 2025-12-04T08:34:37.5843237Z Parse users from the rollout state. 2025-12-04T08:34:37.5843556Z 2025-12-04T08:34:37.5843694Z """ 2025-12-04T08:34:37.5844169Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:34:37.5844849Z return parse_user_opt_in_from_text(users_text) 2025-12-04T08:34:37.5845213Z 2025-12-04T08:34:37.5845219Z 2025-12-04T08:34:37.5845730Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:34:37.5846416Z """ 2025-12-04T08:34:37.5846791Z Check if a user is opted into an experiment 2025-12-04T08:34:37.5847285Z """ 2025-12-04T08:34:37.5847686Z return experiment_name in user_optins.get(user, []) 2025-12-04T08:34:37.5848074Z 2025-12-04T08:34:37.5848080Z 2025-12-04T08:34:37.5848457Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:34:37.5849140Z """ 2025-12-04T08:34:37.5849549Z Check if a user explicitly opted out of an experiment 2025-12-04T08:34:37.5850079Z """ 2025-12-04T08:34:37.5850525Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:34:37.5851146Z experiment_optout = "-" + experiment_name 2025-12-04T08:34:37.5851832Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:34:37.5852382Z return False 2025-12-04T08:34:37.5852615Z 2025-12-04T08:34:37.5852865Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:34:37.5853399Z log.warning( 2025-12-04T08:34:37.5854143Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:34:37.5854944Z ) 2025-12-04T08:34:37.5855130Z 2025-12-04T08:34:37.5855275Z return True 2025-12-04T08:34:37.5855482Z 2025-12-04T08:34:37.5855488Z 2025-12-04T08:34:37.5855645Z def get_runner_prefix( 2025-12-04T08:34:37.5856034Z rollout_state: str, 2025-12-04T08:34:37.5856448Z workflow_requestors: Iterable[str], 2025-12-04T08:34:37.5856913Z branch: str, 2025-12-04T08:34:37.5857351Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:34:37.5857968Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:34:37.5858646Z is_canary: bool = False, 2025-12-04T08:34:37.5859054Z ) -> str: 2025-12-04T08:34:37.5859428Z settings = parse_settings(rollout_state) 2025-12-04T08:34:37.5860090Z user_optins = parse_users(rollout_state) 2025-12-04T08:34:37.5860435Z 2025-12-04T08:34:37.5860590Z fleet_prefix = "" 2025-12-04T08:34:37.5860965Z prefixes = [] 2025-12-04T08:34:37.5861533Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:34:37.5862511Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:34:37.5863154Z log.info( 2025-12-04T08:34:37.5863769Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:34:37.5864457Z ) 2025-12-04T08:34:37.5864807Z continue 2025-12-04T08:34:37.5865034Z 2025-12-04T08:34:37.5865200Z if opt_out_experiments: 2025-12-04T08:34:37.5865675Z if experiment_name in opt_out_experiments: 2025-12-04T08:34:37.5866257Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:34:37.5866788Z log.info( 2025-12-04T08:34:37.5867640Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:34:37.5868531Z ) 2025-12-04T08:34:37.5868882Z continue 2025-12-04T08:34:37.5869121Z 2025-12-04T08:34:37.5869288Z if eligible_experiments: 2025-12-04T08:34:37.5869815Z if experiment_name not in eligible_experiments: 2025-12-04T08:34:37.5870396Z exp_list = ", ".join(eligible_experiments) 2025-12-04T08:34:37.5870903Z log.info( 2025-12-04T08:34:37.5871821Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:34:37.5872652Z ) 2025-12-04T08:34:37.5873016Z continue 2025-12-04T08:34:37.5873435Z elif not experiment_settings.default: 2025-12-04T08:34:37.5873922Z log.info( 2025-12-04T08:34:37.5874667Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:34:37.5875350Z ) 2025-12-04T08:34:37.5875689Z continue 2025-12-04T08:34:37.5875914Z 2025-12-04T08:34:37.5876161Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:34:37.5876719Z opted_out_users = [ 2025-12-04T08:34:37.5877112Z requestor 2025-12-04T08:34:37.5877527Z for requestor in workflow_requestors 2025-12-04T08:34:37.5878142Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:34:37.5878713Z ] 2025-12-04T08:34:37.5878897Z 2025-12-04T08:34:37.5879060Z if opted_out_users: 2025-12-04T08:34:37.5879457Z log.info( 2025-12-04T08:34:37.5880019Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:34:37.5880648Z ) 2025-12-04T08:34:37.5880984Z continue 2025-12-04T08:34:37.5881206Z 2025-12-04T08:34:37.5881448Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:34:37.5882107Z opted_in_users = [ 2025-12-04T08:34:37.5882530Z requestor 2025-12-04T08:34:37.5882938Z for requestor in workflow_requestors 2025-12-04T08:34:37.5883532Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:34:37.5884085Z ] 2025-12-04T08:34:37.5884268Z 2025-12-04T08:34:37.5884423Z enabled = False 2025-12-04T08:34:37.5884816Z if opted_in_users: 2025-12-04T08:34:37.5885202Z log.info( 2025-12-04T08:34:37.5885742Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:34:37.5886359Z ) 2025-12-04T08:34:37.5886698Z enabled = True 2025-12-04T08:34:37.5886956Z 2025-12-04T08:34:37.5887150Z elif experiment_settings.rollout_perc: 2025-12-04T08:34:37.5887904Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:34:37.5888881Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:34:37.5889474Z log.info( 2025-12-04T08:34:37.5890255Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:34:37.5891120Z ) 2025-12-04T08:34:37.5891477Z enabled = True 2025-12-04T08:34:37.5891852Z 2025-12-04T08:34:37.5892000Z if enabled: 2025-12-04T08:34:37.5892383Z label = experiment_name 2025-12-04T08:34:37.5892878Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:34:37.5893630Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:34:37.5894433Z # - If it's enabled, then we always list it's prefix first 2025-12-04T08:34:37.5895136Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:34:37.5895734Z if is_canary: 2025-12-04T08:34:37.5896177Z label += CANARY_FLEET_SUFFIX 2025-12-04T08:34:37.5896669Z fleet_prefix = label 2025-12-04T08:34:37.5897107Z else: 2025-12-04T08:34:37.5897491Z prefixes.append(label) 2025-12-04T08:34:37.5897805Z 2025-12-04T08:34:37.5897969Z if len(prefixes) > 1: 2025-12-04T08:34:37.5898364Z log.error( 2025-12-04T08:34:37.5899295Z 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-12-04T08:34:37.5900317Z ) 2025-12-04T08:34:37.5900658Z prefixes = prefixes[:1] 2025-12-04T08:34:37.5900942Z 2025-12-04T08:34:37.5901104Z # Fleet always comes first 2025-12-04T08:34:37.5901522Z if fleet_prefix: 2025-12-04T08:34:37.5902019Z prefixes.insert(0, fleet_prefix) 2025-12-04T08:34:37.5902345Z 2025-12-04T08:34:37.5902692Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:34:37.5903075Z 2025-12-04T08:34:37.5903081Z 2025-12-04T08:34:37.5903479Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:34:37.5904184Z """ 2025-12-04T08:34:37.5904707Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:34:37.5905223Z 2025-12-04T08:34:37.5905570Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:34:37.5906204Z """ 2025-12-04T08:34:37.5906541Z gh = get_gh_client(github_token) 2025-12-04T08:34:37.5907023Z issue = get_issue(gh, repo, issue_num) 2025-12-04T08:34:37.5907591Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:34:37.5907993Z 2025-12-04T08:34:37.5908000Z 2025-12-04T08:34:37.5908356Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:34:37.5909046Z for _ in range(num_retries): 2025-12-04T08:34:37.5909469Z try: 2025-12-04T08:34:37.5909845Z req = Request(url=url, headers=headers) 2025-12-04T08:34:37.5910441Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:34:37.5911020Z return json.loads(content) 2025-12-04T08:34:37.5911488Z except Exception as e: 2025-12-04T08:34:37.5912251Z log.warning(f"Could not download {url}: {e}") 2025-12-04T08:34:37.5912624Z 2025-12-04T08:34:37.5912970Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:34:37.5913615Z return {} 2025-12-04T08:34:37.5913812Z 2025-12-04T08:34:37.5913818Z 2025-12-04T08:34:37.5913962Z @cache 2025-12-04T08:34:37.5914518Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:34:37.5915198Z """ 2025-12-04T08:34:37.5915545Z Dynamically get PR information 2025-12-04T08:34:37.5915986Z """ 2025-12-04T08:34:37.5916567Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:34:37.5917135Z headers = { 2025-12-04T08:34:37.5917542Z "Accept": "application/vnd.github.v3+json", 2025-12-04T08:34:37.5918091Z "Authorization": f"token {github_token}", 2025-12-04T08:34:37.5918574Z } 2025-12-04T08:34:37.5918948Z json_response: dict[str, Any] = download_json( 2025-12-04T08:34:37.5919506Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:34:37.5919997Z headers=headers, 2025-12-04T08:34:37.5920377Z ) 2025-12-04T08:34:37.5920554Z 2025-12-04T08:34:37.5920714Z if not json_response: 2025-12-04T08:34:37.5921231Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:34:37.5921902Z return {} 2025-12-04T08:34:37.5922119Z 2025-12-04T08:34:37.5922279Z return json_response 2025-12-04T08:34:37.5922530Z 2025-12-04T08:34:37.5922536Z 2025-12-04T08:34:37.5922904Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:34:37.5923565Z """ 2025-12-04T08:34:37.5924037Z Dynamically get the latest list of labels from the pull request 2025-12-04T08:34:37.5924618Z """ 2025-12-04T08:34:37.5925054Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:34:37.5925598Z return { 2025-12-04T08:34:37.5926118Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:34:37.5926756Z } 2025-12-04T08:34:37.5926939Z 2025-12-04T08:34:37.5926945Z 2025-12-04T08:34:37.5927097Z def main() -> None: 2025-12-04T08:34:37.5927475Z args = parse_args() 2025-12-04T08:34:37.5927712Z 2025-12-04T08:34:37.5927907Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:34:37.5928257Z 2025-12-04T08:34:37.5928426Z # Check if the PR is opt-out 2025-12-04T08:34:37.5928862Z if args.pr_number: 2025-12-04T08:34:37.5929449Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:34:37.5930249Z if OPT_OUT_LABEL in labels: 2025-12-04T08:34:37.5930696Z log.info( 2025-12-04T08:34:37.5931319Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:34:37.5932117Z ) 2025-12-04T08:34:37.5932610Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:34:37.5933210Z sys.exit() 2025-12-04T08:34:37.5933449Z 2025-12-04T08:34:37.5933591Z try: 2025-12-04T08:34:37.5933972Z rollout_state = get_rollout_state_from_issue( 2025-12-04T08:34:37.5934606Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:34:37.5935180Z ) 2025-12-04T08:34:37.5935366Z 2025-12-04T08:34:37.5935548Z username = get_potential_pr_author( 2025-12-04T08:34:37.5936052Z args.github_token, 2025-12-04T08:34:37.5936474Z args.github_repo, 2025-12-04T08:34:37.5936898Z args.github_actor, 2025-12-04T08:34:37.5937326Z args.github_ref_type, 2025-12-04T08:34:37.5937780Z args.github_branch, 2025-12-04T08:34:37.5938176Z ) 2025-12-04T08:34:37.5938364Z 2025-12-04T08:34:37.5938615Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:34:37.5939025Z 2025-12-04T08:34:37.5939222Z runner_label_prefix = get_runner_prefix( 2025-12-04T08:34:37.5939714Z rollout_state, 2025-12-04T08:34:37.5940151Z (args.github_issue_owner, username), 2025-12-04T08:34:37.5940645Z args.github_branch, 2025-12-04T08:34:37.5941090Z args.eligible_experiments, 2025-12-04T08:34:37.5941663Z args.opt_out_experiments, 2025-12-04T08:34:37.5942120Z is_canary, 2025-12-04T08:34:37.5942485Z ) 2025-12-04T08:34:37.5942674Z 2025-12-04T08:34:37.5942836Z except Exception as e: 2025-12-04T08:34:37.5943233Z log.error( 2025-12-04T08:34:37.5943827Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:34:37.5944634Z ) 2025-12-04T08:34:37.5944823Z 2025-12-04T08:34:37.5945121Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:34:37.5945579Z 2025-12-04T08:34:37.5945585Z 2025-12-04T08:34:37.5945741Z if __name__ == "__main__": 2025-12-04T08:34:37.5946128Z main() 2025-12-04T08:34:37.5946319Z 2025-12-04T08:34:37.6033157Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:34:37.6033983Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:34:37.6065529Z shell: /usr/bin/bash -e {0} 2025-12-04T08:34:37.6065971Z env: 2025-12-04T08:34:37.6066526Z GITHUB_TOKEN: *** 2025-12-04T08:34:37.6066906Z ISSUE_NUMBER: 5132 2025-12-04T08:34:37.6067309Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:34:37.6067761Z ISSUE_OWNER: 2025-12-04T08:34:37.6068126Z CHECK_EXPERIMENTS: 2025-12-04T08:34:37.6068515Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:34:37.6068903Z PR_NUMBER: 2025-12-04T08:34:37.6069251Z ##[endgroup] 2025-12-04T08:34:38.0209388Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T08:34:38.4503013Z Collecting urllib3==1.26.18 2025-12-04T08:34:38.4831420Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T08:34:38.5063293Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.1 MB/s eta 0:00:00 2025-12-04T08:34:38.5289803Z Collecting PyGithub==2.3.0 2025-12-04T08:34:38.5322718Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T08:34:38.5761015Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T08:34:38.5794152Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T08:34:38.5838190Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T08:34:38.5854925Z 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-12-04T08:34:38.5869363Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-12-04T08:34:38.6118249Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T08:34:38.6148227Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T08:34:38.6370586Z 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-12-04T08:34:38.7618351Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:34:38.7649538Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T08:34:38.9237227Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T08:34:38.9270839Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (9.0 kB) 2025-12-04T08:34:38.9463646Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:34:38.9494625Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T08:34:38.9735809Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T08:34:38.9796245Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 35.8 MB/s eta 0:00:00 2025-12-04T08:34:38.9849451Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T08:34:38.9904087Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 90.0 MB/s eta 0:00:00 2025-12-04T08:34:38.9936770Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T08:34:39.0227998Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 51.1 MB/s eta 0:00:00 2025-12-04T08:34:39.0260627Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T08:34:39.0312696Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T08:34:39.0359343Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 69.0 MB/s eta 0:00:00 2025-12-04T08:34:39.0392129Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (121 kB) 2025-12-04T08:34:39.0433746Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 44.7 MB/s eta 0:00:00 2025-12-04T08:34:39.0464857Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T08:34:39.0505859Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 43.6 MB/s eta 0:00:00 2025-12-04T08:34:39.3393931Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T08:34:39.8740662Z Successfully installed Deprecated-1.3.1 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.1 urllib3-1.26.18 wrapt-2.0.1 2025-12-04T08:34:39.9547456Z ##[group]Run curr_branch="main" 2025-12-04T08:34:39.9547811Z curr_branch="main" 2025-12-04T08:34:39.9548072Z curr_ref_type="branch" 2025-12-04T08:34:39.9548364Z echo "Current branch is '$curr_branch'" 2025-12-04T08:34:39.9548695Z  2025-12-04T08:34:39.9548924Z python3 runner_determinator.py \ 2025-12-04T08:34:39.9549237Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T08:34:39.9549551Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T08:34:39.9549848Z  --github-branch "$curr_branch" \ 2025-12-04T08:34:39.9550147Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T08:34:39.9550468Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T08:34:39.9550780Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T08:34:39.9551092Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T08:34:39.9551439Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T08:34:39.9552117Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T08:34:39.9552447Z  --pr-number "${PR_NUMBER}" 2025-12-04T08:34:39.9585532Z shell: /usr/bin/bash -e {0} 2025-12-04T08:34:39.9585800Z env: 2025-12-04T08:34:39.9586464Z GITHUB_TOKEN: *** 2025-12-04T08:34:39.9586701Z ISSUE_NUMBER: 5132 2025-12-04T08:34:39.9586941Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T08:34:39.9587220Z ISSUE_OWNER: 2025-12-04T08:34:39.9587444Z CHECK_EXPERIMENTS: 2025-12-04T08:34:39.9587683Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:34:39.9587915Z PR_NUMBER: 2025-12-04T08:34:39.9588123Z ##[endgroup] 2025-12-04T08:34:39.9639313Z Current branch is 'main' 2025-12-04T08:34:41.8139045Z INFO : Based on rollout percentage of 75%, enabling experiment lf. 2025-12-04T08:34:41.8140821Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T08:34:41.8141932Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T08:34:41.8142608Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T08:34:41.8143105Z INFO : Setting output: label-type='lf.' 2025-12-04T08:34:41.8472171Z Evaluate and set job outputs 2025-12-04T08:34:41.8478999Z Set output 'label-type' 2025-12-04T08:34:41.8480772Z Cleaning up orphan processes