2025-06-01T20:56:25.6832814Z Current runner version: '2.324.0' 2025-06-01T20:56:25.6855816Z ##[group]Operating System 2025-06-01T20:56:25.6856734Z Ubuntu 2025-06-01T20:56:25.6857235Z 24.04.2 2025-06-01T20:56:25.6857724Z LTS 2025-06-01T20:56:25.6858215Z ##[endgroup] 2025-06-01T20:56:25.6858684Z ##[group]Runner Image 2025-06-01T20:56:25.6859276Z Image: ubuntu-24.04 2025-06-01T20:56:25.6859887Z Version: 20250527.1.0 2025-06-01T20:56:25.6860871Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250527.1/images/ubuntu/Ubuntu2404-Readme.md 2025-06-01T20:56:25.6862266Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250527.1 2025-06-01T20:56:25.6863135Z ##[endgroup] 2025-06-01T20:56:25.6863632Z ##[group]Runner Image Provisioner 2025-06-01T20:56:25.6864151Z 2.0.437.1 2025-06-01T20:56:25.6864709Z ##[endgroup] 2025-06-01T20:56:25.6867280Z ##[group]GITHUB_TOKEN Permissions 2025-06-01T20:56:25.6869509Z Actions: read 2025-06-01T20:56:25.6870195Z Attestations: read 2025-06-01T20:56:25.6870857Z Checks: read 2025-06-01T20:56:25.6871310Z Contents: read 2025-06-01T20:56:25.6871835Z Deployments: read 2025-06-01T20:56:25.6872369Z Discussions: read 2025-06-01T20:56:25.6872860Z Issues: read 2025-06-01T20:56:25.6873291Z Metadata: read 2025-06-01T20:56:25.6873810Z Models: read 2025-06-01T20:56:25.6874245Z Packages: read 2025-06-01T20:56:25.6874756Z Pages: read 2025-06-01T20:56:25.6875220Z PullRequests: read 2025-06-01T20:56:25.6876056Z RepositoryProjects: read 2025-06-01T20:56:25.6876606Z SecurityEvents: read 2025-06-01T20:56:25.6877130Z Statuses: read 2025-06-01T20:56:25.6877634Z ##[endgroup] 2025-06-01T20:56:25.6879690Z Secret source: Actions 2025-06-01T20:56:25.6880528Z Prepare workflow directory 2025-06-01T20:56:25.7437781Z Prepare all required actions 2025-06-01T20:56:25.7489676Z Complete job name: get-label-type / runner-determinator 2025-06-01T20:56:25.8061714Z ##[group]Run cat < runner_determinator.py 2025-06-01T20:56:25.8064159Z cat < runner_determinator.py 2025-06-01T20:56:25.8064902Z # flake8: noqa: G004 2025-06-01T20:56:25.8065669Z  2025-06-01T20:56:25.8066456Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:25.8067508Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:25.8068420Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:25.8069071Z  2025-06-01T20:56:25.8069542Z """ 2025-06-01T20:56:25.8070240Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:25.8071216Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:25.8072365Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:25.8073266Z of which runners should be used to run which job. 2025-06-01T20:56:25.8073895Z  2025-06-01T20:56:25.8074598Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:25.8075727Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:25.8076716Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:25.8077531Z list, defined. 2025-06-01T20:56:25.8078023Z  2025-06-01T20:56:25.8078625Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:25.8079694Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:25.8080666Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:25.8081320Z  2025-06-01T20:56:25.8082024Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:25.8082959Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:25.8084378Z experiments which the user could be opted in to. 2025-06-01T20:56:25.8085816Z  2025-06-01T20:56:25.8086897Z The user list has the following rules: 2025-06-01T20:56:25.8087998Z  2025-06-01T20:56:25.8089188Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:25.8091031Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:25.8092651Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:25.8093957Z  2025-06-01T20:56:25.8094875Z Example config: 2025-06-01T20:56:25.8096170Z  # A list of experiments that can be opted into. 2025-06-01T20:56:25.8097617Z  # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:25.8098805Z  # Expected syntax is: 2025-06-01T20:56:25.8100124Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:25.8102166Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:25.8103099Z  2025-06-01T20:56:25.8103546Z  experiments: 2025-06-01T20:56:25.8104004Z  lf: 2025-06-01T20:56:25.8104590Z  rollout_percent: 25 2025-06-01T20:56:25.8105132Z  all_branches: false 2025-06-01T20:56:25.8106003Z  default: true 2025-06-01T20:56:25.8106607Z  --- 2025-06-01T20:56:25.8107074Z  2025-06-01T20:56:25.8107497Z  # Opt-ins: 2025-06-01T20:56:25.8108242Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:25.8109422Z  # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:25.8110278Z  # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:25.8111132Z  # Experiments should be from the above list. 2025-06-01T20:56:25.8111763Z  2025-06-01T20:56:25.8112179Z  @User1,-lf,split_build 2025-06-01T20:56:25.8112789Z  @User2,lf 2025-06-01T20:56:25.8113261Z  @User3,split_build 2025-06-01T20:56:25.8113797Z """ 2025-06-01T20:56:25.8114273Z  2025-06-01T20:56:25.8114732Z import json 2025-06-01T20:56:25.8115191Z import logging 2025-06-01T20:56:25.8115944Z import os 2025-06-01T20:56:25.8116512Z import random 2025-06-01T20:56:25.8116981Z import re 2025-06-01T20:56:25.8117538Z import sys 2025-06-01T20:56:25.8118082Z from argparse import ArgumentParser 2025-06-01T20:56:25.8118698Z from collections.abc import Iterable 2025-06-01T20:56:25.8119361Z from functools import cache 2025-06-01T20:56:25.8119939Z from logging import LogRecord 2025-06-01T20:56:25.8120556Z from typing import Any, NamedTuple 2025-06-01T20:56:25.8121214Z from urllib.request import Request, urlopen 2025-06-01T20:56:25.8121901Z  2025-06-01T20:56:25.8122305Z import yaml 2025-06-01T20:56:25.8122804Z from github import Auth, Github 2025-06-01T20:56:25.8123474Z from github.Issue import Issue 2025-06-01T20:56:25.8124005Z  2025-06-01T20:56:25.8124452Z  2025-06-01T20:56:25.8124994Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:25.8126044Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:25.8127012Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:25.8128012Z  2025-06-01T20:56:25.8128933Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:25.8130008Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:25.8131176Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:25.8132514Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:25.8133516Z  2025-06-01T20:56:25.8134370Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:25.8135589Z  2025-06-01T20:56:25.8136886Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:25.8137861Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:25.8138802Z  2025-06-01T20:56:25.8139428Z  2025-06-01T20:56:25.8140224Z class Experiment(NamedTuple): 2025-06-01T20:56:25.8141189Z  rollout_perc: float = ( 2025-06-01T20:56:25.8142589Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:25.8143975Z  ) 2025-06-01T20:56:25.8144757Z  all_branches: bool = ( 2025-06-01T20:56:25.8146344Z  False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:25.8147723Z  ) 2025-06-01T20:56:25.8148689Z  default: bool = ( 2025-06-01T20:56:25.8150035Z  True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:25.8151324Z  ) 2025-06-01T20:56:25.8152239Z  2025-06-01T20:56:25.8153000Z  # Add more fields as needed 2025-06-01T20:56:25.8154023Z  2025-06-01T20:56:25.8154873Z  2025-06-01T20:56:25.8155981Z class Settings(NamedTuple): 2025-06-01T20:56:25.8156935Z  """ 2025-06-01T20:56:25.8158113Z  Settings for the experiments that can be opted into. 2025-06-01T20:56:25.8159314Z  """ 2025-06-01T20:56:25.8160128Z  2025-06-01T20:56:25.8161441Z  experiments: dict[str, Experiment] = {} 2025-06-01T20:56:25.8162549Z  2025-06-01T20:56:25.8163535Z  2025-06-01T20:56:25.8164597Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:25.8166210Z  """Color codes the log messages based on the log level""" 2025-06-01T20:56:25.8167481Z  2025-06-01T20:56:25.8168351Z  COLORS = { 2025-06-01T20:56:25.8169218Z  "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:25.8170179Z  "ERROR": "\033[31m", # Red 2025-06-01T20:56:25.8171373Z  "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:25.8172441Z  "INFO": "\033[0m", # Reset 2025-06-01T20:56:25.8173622Z  "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:25.8174873Z  } 2025-06-01T20:56:25.8175834Z  2025-06-01T20:56:25.8176756Z  def format(self, record: LogRecord) -> str: 2025-06-01T20:56:25.8178331Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:25.8179973Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:25.8181164Z  return super().format(record) 2025-06-01T20:56:25.8182341Z  2025-06-01T20:56:25.8183152Z  2025-06-01T20:56:25.8183915Z handler = logging.StreamHandler() 2025-06-01T20:56:25.8185688Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:25.8187215Z  2025-06-01T20:56:25.8188171Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:25.8189365Z log.addHandler(handler) 2025-06-01T20:56:25.8190426Z log.setLevel(logging.INFO) 2025-06-01T20:56:25.8191392Z  2025-06-01T20:56:25.8192183Z  2025-06-01T20:56:25.8193210Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:25.8194333Z  """ 2025-06-01T20:56:25.8195702Z  Defines outputs of the github action that invokes this script 2025-06-01T20:56:25.8197040Z  """ 2025-06-01T20:56:25.8198023Z  if not GITHUB_OUTPUT: 2025-06-01T20:56:25.8200425Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:25.8202748Z  log.warning( 2025-06-01T20:56:25.8204435Z  "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:25.8206568Z  ) 2025-06-01T20:56:25.8207566Z  print(f"::set-output name={key}::{value}") 2025-06-01T20:56:25.8208700Z  return 2025-06-01T20:56:25.8209593Z  2025-06-01T20:56:25.8210373Z  with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:25.8211450Z  log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:25.8212762Z  f.write(f"{key}={value}\n") 2025-06-01T20:56:25.8213679Z  2025-06-01T20:56:25.8214355Z  2025-06-01T20:56:25.8215877Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:25.8217138Z  return frozenset( 2025-06-01T20:56:25.8218360Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:25.8219783Z  ) 2025-06-01T20:56:25.8220550Z  2025-06-01T20:56:25.8221185Z  2025-06-01T20:56:25.8222062Z def parse_args() -> Any: 2025-06-01T20:56:25.8223213Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:25.8224820Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:25.8226671Z  parser.add_argument( 2025-06-01T20:56:25.8227594Z  "--github-issue-repo", 2025-06-01T20:56:25.8228594Z  type=str, 2025-06-01T20:56:25.8229537Z  required=False, 2025-06-01T20:56:25.8230693Z  default="pytorch/test-infra", 2025-06-01T20:56:25.8231848Z  help="GitHub repo to get the issue", 2025-06-01T20:56:25.8232984Z  ) 2025-06-01T20:56:25.8233737Z  parser.add_argument( 2025-06-01T20:56:25.8234672Z  "--github-repo", 2025-06-01T20:56:25.8235893Z  type=str, 2025-06-01T20:56:25.8236700Z  required=True, 2025-06-01T20:56:25.8237688Z  help="GitHub repo where CI is running", 2025-06-01T20:56:25.8238912Z  ) 2025-06-01T20:56:25.8239631Z  parser.add_argument( 2025-06-01T20:56:25.8240868Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:25.8242203Z  ) 2025-06-01T20:56:25.8242958Z  parser.add_argument( 2025-06-01T20:56:25.8244218Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:25.8245992Z  ) 2025-06-01T20:56:25.8246780Z  parser.add_argument( 2025-06-01T20:56:25.8248059Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:25.8249507Z  ) 2025-06-01T20:56:25.8250230Z  parser.add_argument( 2025-06-01T20:56:25.8251578Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:25.8253082Z  ) 2025-06-01T20:56:25.8253921Z  parser.add_argument( 2025-06-01T20:56:25.8254813Z  "--github-ref-type", 2025-06-01T20:56:25.8256038Z  type=str, 2025-06-01T20:56:25.8256879Z  required=True, 2025-06-01T20:56:25.8324279Z  help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:25.8325530Z  ) 2025-06-01T20:56:25.8326193Z  parser.add_argument( 2025-06-01T20:56:25.8327035Z  "--eligible-experiments", 2025-06-01T20:56:25.8327973Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:25.8328878Z  required=False, 2025-06-01T20:56:25.8329897Z  default="", 2025-06-01T20:56:25.8331403Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:25.8333023Z  ) 2025-06-01T20:56:25.8333653Z  parser.add_argument( 2025-06-01T20:56:25.8334487Z  "--opt-out-experiments", 2025-06-01T20:56:25.8335788Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:25.8336714Z  required=False, 2025-06-01T20:56:25.8337471Z  default="", 2025-06-01T20:56:25.8338191Z  help=( 2025-06-01T20:56:25.8339625Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:25.8341593Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:25.8343051Z  ), 2025-06-01T20:56:25.8343675Z  ) 2025-06-01T20:56:25.8344314Z  parser.add_argument( 2025-06-01T20:56:25.8345116Z  "--pr-number", 2025-06-01T20:56:25.8346038Z  type=str, 2025-06-01T20:56:25.8346766Z  required=False, 2025-06-01T20:56:25.8347509Z  default="", 2025-06-01T20:56:25.8348379Z  help="the optional PR number where this is run", 2025-06-01T20:56:25.8349344Z  ) 2025-06-01T20:56:25.8349951Z  2025-06-01T20:56:25.8350572Z  return parser.parse_args() 2025-06-01T20:56:25.8351364Z  2025-06-01T20:56:25.8351918Z  2025-06-01T20:56:25.8352945Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:25.8354478Z  auth = Auth.Token(github_token) 2025-06-01T20:56:25.8355575Z  return Github(auth=auth) 2025-06-01T20:56:25.8356402Z  2025-06-01T20:56:25.8356957Z  2025-06-01T20:56:25.8358072Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:25.8359454Z  repo = gh.get_repo(repo) 2025-06-01T20:56:25.8360425Z  return repo.get_issue(number=issue_num) 2025-06-01T20:56:25.8361291Z  2025-06-01T20:56:25.8361838Z  2025-06-01T20:56:25.8362478Z def get_potential_pr_author( 2025-06-01T20:56:25.8363617Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:25.8364766Z ) -> str: 2025-06-01T20:56:25.8366451Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:25.8367873Z  # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:25.8369230Z  # embedded in the tag name: ciflow// 2025-06-01T20:56:25.8370213Z  2025-06-01T20:56:25.8370843Z  gh = get_gh_client(github_token) 2025-06-01T20:56:25.8371665Z  2025-06-01T20:56:25.8372467Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:25.8373552Z  split_tag = ref_name.split("/") 2025-06-01T20:56:25.8374397Z  if ( 2025-06-01T20:56:25.8375078Z  len(split_tag) == 3 2025-06-01T20:56:25.8376149Z  and split_tag[0] == "ciflow" 2025-06-01T20:56:25.8377062Z  and split_tag[2].isnumeric() 2025-06-01T20:56:25.8377893Z  ): 2025-06-01T20:56:25.8378577Z  pr_number = split_tag[2] 2025-06-01T20:56:25.8379415Z  try: 2025-06-01T20:56:25.8380195Z  repository = gh.get_repo(repo) 2025-06-01T20:56:25.8381252Z  pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:25.8382307Z  except Exception as e: 2025-06-01T20:56:25.8383236Z  raise Exception( # noqa: TRY002 2025-06-01T20:56:25.8384631Z  f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:25.8385983Z  ) from e 2025-06-01T20:56:25.8386969Z  return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:25.8388178Z  # In all other cases, return the original input username 2025-06-01T20:56:25.8389219Z  return username 2025-06-01T20:56:25.8389922Z  2025-06-01T20:56:25.8390470Z  2025-06-01T20:56:25.8391166Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:25.8392086Z  """ 2025-06-01T20:56:25.8393224Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:25.8394546Z  """ 2025-06-01T20:56:25.8395701Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:25.8396854Z  2025-06-01T20:56:25.8397405Z  2025-06-01T20:56:25.8398045Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:25.8398925Z  try: 2025-06-01T20:56:25.8399598Z  data = yaml.safe_load(yaml_text) 2025-06-01T20:56:25.8400462Z  return data 2025-06-01T20:56:25.8401217Z  except yaml.YAMLError: 2025-06-01T20:56:25.8402102Z  log.exception("Error loading YAML") 2025-06-01T20:56:25.8402992Z  raise 2025-06-01T20:56:25.8403639Z  2025-06-01T20:56:25.8404183Z  2025-06-01T20:56:25.8405232Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:25.8406728Z  """ 2025-06-01T20:56:25.8408016Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:25.8409353Z  2025-06-01T20:56:25.8410271Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:25.8411587Z  and the text below is the list of opted in users. 2025-06-01T20:56:25.8412540Z  2025-06-01T20:56:25.8413487Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:25.8414676Z  """ 2025-06-01T20:56:25.8415649Z  rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:25.8416688Z  if len(rollout_state_parts) >= 2: 2025-06-01T20:56:25.8417751Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:25.8418771Z  else: 2025-06-01T20:56:25.8419451Z  return "", rollout_state 2025-06-01T20:56:25.8420232Z  2025-06-01T20:56:25.8420780Z  2025-06-01T20:56:25.8421463Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:25.8422332Z  """ 2025-06-01T20:56:25.8423242Z  Dictionary of users with a list of features they have opted into 2025-06-01T20:56:25.8424351Z  """ 2025-06-01T20:56:25.8424956Z  2025-06-01T20:56:25.8425709Z  2025-06-01T20:56:25.8426609Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:25.8427749Z  """ 2025-06-01T20:56:25.8429016Z  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:25.8430441Z  2025-06-01T20:56:25.8431812Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:25.8433537Z  - Example line: "@User1,lf,split_build" 2025-06-01T20:56:25.8434760Z  - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:25.8436038Z  2025-06-01T20:56:25.8436576Z  2025-06-01T20:56:25.8437116Z  """ 2025-06-01T20:56:25.8438017Z  optins = UserOptins() 2025-06-01T20:56:25.8438901Z  for user in user_optin_text.split("\n"): 2025-06-01T20:56:25.8439853Z  user = user.strip("\r\n\t -") 2025-06-01T20:56:25.8440783Z  if not user or not user.startswith("@"): 2025-06-01T20:56:25.8441736Z  # Not a valid user. Skip 2025-06-01T20:56:25.8442569Z  continue 2025-06-01T20:56:25.8443250Z  2025-06-01T20:56:25.8443808Z  if user: 2025-06-01T20:56:25.8444620Z  usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:25.8446045Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:25.8447158Z  2025-06-01T20:56:25.8447757Z  return optins 2025-06-01T20:56:25.8448437Z  2025-06-01T20:56:25.8448971Z  2025-06-01T20:56:25.8449798Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:25.8450931Z  """ 2025-06-01T20:56:25.8451658Z  Check if the experiment name is valid. 2025-06-01T20:56:25.8452545Z  A valid name: 2025-06-01T20:56:25.8453691Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:25.8455572Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:25.8456898Z  - Cannot contain spaces 2025-06-01T20:56:25.8457684Z  """ 2025-06-01T20:56:25.8458267Z  2025-06-01T20:56:25.8459025Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:25.8460322Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:25.8461534Z  2025-06-01T20:56:25.8462098Z  if valid: 2025-06-01T20:56:25.8462766Z  return True 2025-06-01T20:56:25.8463470Z  2025-06-01T20:56:25.8464054Z  log.error( 2025-06-01T20:56:25.8466802Z  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:25.8469469Z  ) 2025-06-01T20:56:25.8470095Z  return False 2025-06-01T20:56:25.8470776Z  2025-06-01T20:56:25.8471324Z  2025-06-01T20:56:25.8472167Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:25.8473269Z  """ 2025-06-01T20:56:25.8474289Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:25.8475676Z  """ 2025-06-01T20:56:25.8476304Z  try: 2025-06-01T20:56:25.8476942Z  if settings_text: 2025-06-01T20:56:25.8478220Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:25.8479599Z  # for easy reading 2025-06-01T20:56:25.8481006Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:25.8482553Z  # the backtick character in shell commands. 2025-06-01T20:56:25.8483601Z  backtick = chr(96) # backtick character 2025-06-01T20:56:25.8484745Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:25.8486093Z  settings = load_yaml(settings_text) 2025-06-01T20:56:25.8486956Z  2025-06-01T20:56:25.8487957Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:25.8489265Z  experiments = {} 2025-06-01T20:56:25.8490039Z  2025-06-01T20:56:25.8490965Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:25.8492493Z  if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:25.8494356Z  # 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:25.8496351Z  continue 2025-06-01T20:56:25.8497127Z  2025-06-01T20:56:25.8497724Z  valid_settings = {} 2025-06-01T20:56:25.8498640Z  for setting in exp_settings: 2025-06-01T20:56:25.8499606Z  if setting not in Experiment._fields: 2025-06-01T20:56:25.8500548Z  log.warning( 2025-06-01T20:56:25.8501807Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:25.8503030Z  ) 2025-06-01T20:56:25.8503778Z  else: 2025-06-01T20:56:25.8504699Z  valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:25.8505944Z  2025-06-01T20:56:25.8506745Z  experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:25.8507845Z  return Settings(experiments) 2025-06-01T20:56:25.8508694Z  2025-06-01T20:56:25.8509289Z  except Exception: 2025-06-01T20:56:25.8510153Z  log.exception("Failed to parse settings") 2025-06-01T20:56:25.8511056Z  2025-06-01T20:56:25.8511668Z  return Settings() 2025-06-01T20:56:25.8512370Z  2025-06-01T20:56:25.8512919Z  2025-06-01T20:56:25.8513856Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:25.8514881Z  """ 2025-06-01T20:56:25.8515838Z  Parse settings, if any, from the rollout state. 2025-06-01T20:56:25.8516785Z  2025-06-01T20:56:25.8517678Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:25.8518985Z  and the text below is the list of opted in users. 2025-06-01T20:56:25.8519923Z  2025-06-01T20:56:25.8520906Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:25.8522170Z  """ 2025-06-01T20:56:25.8523144Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:25.8524444Z  return parse_settings_from_text(settings_text) 2025-06-01T20:56:25.8525579Z  2025-06-01T20:56:25.8526134Z  2025-06-01T20:56:25.8526888Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:25.8527840Z  """ 2025-06-01T20:56:25.8528548Z  Parse users from the rollout state. 2025-06-01T20:56:25.8529415Z  2025-06-01T20:56:25.8529960Z  """ 2025-06-01T20:56:25.8530893Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:25.8532194Z  return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:25.8533107Z  2025-06-01T20:56:25.8533644Z  2025-06-01T20:56:25.8534676Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:25.8536139Z  """ 2025-06-01T20:56:25.8536877Z  Check if a user is opted into an experiment 2025-06-01T20:56:25.8537799Z  """ 2025-06-01T20:56:25.8538618Z  return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:25.8539584Z  2025-06-01T20:56:25.8540129Z  2025-06-01T20:56:25.8541189Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:25.8542483Z  """ 2025-06-01T20:56:25.8543496Z  Check if a user explicitly opted out of an experiment 2025-06-01T20:56:25.8544495Z  """ 2025-06-01T20:56:25.8545612Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:25.8546821Z  experiment_optout = "-" + experiment_name 2025-06-01T20:56:25.8547929Z  if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:25.8548969Z  return False 2025-06-01T20:56:25.8549664Z  2025-06-01T20:56:25.8550425Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:25.8551464Z  log.warning( 2025-06-01T20:56:25.8552853Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:25.8554360Z  ) 2025-06-01T20:56:25.8554973Z  2025-06-01T20:56:25.8555731Z  return True 2025-06-01T20:56:25.8556402Z  2025-06-01T20:56:25.8556948Z  2025-06-01T20:56:25.8557546Z def get_runner_prefix( 2025-06-01T20:56:25.8558342Z  rollout_state: str, 2025-06-01T20:56:25.8559168Z  workflow_requestors: Iterable[str], 2025-06-01T20:56:25.8560029Z  branch: str, 2025-06-01T20:56:25.8560983Z  eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:25.8562159Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:25.8563150Z  is_canary: bool = False, 2025-06-01T20:56:25.8563925Z ) -> str: 2025-06-01T20:56:25.8564695Z  settings = parse_settings(rollout_state) 2025-06-01T20:56:25.8565880Z  user_optins = parse_users(rollout_state) 2025-06-01T20:56:25.8566745Z  2025-06-01T20:56:25.8567550Z  fleet_prefix = "" 2025-06-01T20:56:25.8568340Z  prefixes = [] 2025-06-01T20:56:25.8569469Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:25.8571144Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:25.8572373Z  log.info( 2025-06-01T20:56:25.8573558Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:25.8574831Z  ) 2025-06-01T20:56:25.8575694Z  continue 2025-06-01T20:56:25.8576377Z  2025-06-01T20:56:25.8576987Z  if opt_out_experiments: 2025-06-01T20:56:25.8577942Z  if experiment_name in opt_out_experiments: 2025-06-01T20:56:25.8579028Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:25.8580018Z  log.info( 2025-06-01T20:56:25.8581640Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:25.8583320Z  ) 2025-06-01T20:56:25.8584035Z  continue 2025-06-01T20:56:25.8584777Z  2025-06-01T20:56:25.8585613Z  if eligible_experiments: 2025-06-01T20:56:25.8586595Z  if experiment_name not in eligible_experiments: 2025-06-01T20:56:25.8587711Z  exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:25.8588631Z  log.info( 2025-06-01T20:56:25.8589997Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:25.8591439Z  ) 2025-06-01T20:56:25.8592145Z  continue 2025-06-01T20:56:25.8593004Z  elif not experiment_settings.default: 2025-06-01T20:56:25.8593883Z  log.info( 2025-06-01T20:56:25.8595060Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:25.8596716Z  ) 2025-06-01T20:56:25.8597405Z  continue 2025-06-01T20:56:25.8598120Z  2025-06-01T20:56:25.8598907Z  # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:25.8599945Z  opted_out_users = [ 2025-06-01T20:56:25.8600739Z  requestor 2025-06-01T20:56:25.8601593Z  for requestor in workflow_requestors 2025-06-01T20:56:25.8602739Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:25.8603802Z  ] 2025-06-01T20:56:25.8604423Z  2025-06-01T20:56:25.8605016Z  if opted_out_users: 2025-06-01T20:56:25.8606006Z  log.info( 2025-06-01T20:56:25.8607100Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:25.8608290Z  ) 2025-06-01T20:56:25.8608960Z  continue 2025-06-01T20:56:25.8609645Z  2025-06-01T20:56:25.8610396Z  # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:25.8611458Z  opted_in_users = [ 2025-06-01T20:56:25.8612237Z  requestor 2025-06-01T20:56:25.8613052Z  for requestor in workflow_requestors 2025-06-01T20:56:25.8614218Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:25.8615264Z  ] 2025-06-01T20:56:25.8616057Z  2025-06-01T20:56:25.8616618Z  enabled = False 2025-06-01T20:56:25.8617397Z  if opted_in_users: 2025-06-01T20:56:25.8618370Z  log.info( 2025-06-01T20:56:25.8619451Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:25.8620614Z  ) 2025-06-01T20:56:25.8621282Z  enabled = True 2025-06-01T20:56:25.8622019Z  2025-06-01T20:56:25.8622670Z  elif experiment_settings.rollout_perc: 2025-06-01T20:56:25.8624120Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:25.8625958Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:25.8627078Z  log.info( 2025-06-01T20:56:25.8628610Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:25.8630195Z  ) 2025-06-01T20:56:25.8630940Z  enabled = True 2025-06-01T20:56:25.8631708Z  2025-06-01T20:56:25.8632270Z  if enabled: 2025-06-01T20:56:25.8633052Z  label = experiment_name 2025-06-01T20:56:25.8634031Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:25.8635609Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:25.8637131Z  # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:25.8638437Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:25.8639558Z  if is_canary: 2025-06-01T20:56:25.8640419Z  label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:25.8641332Z  fleet_prefix = label 2025-06-01T20:56:25.8642152Z  else: 2025-06-01T20:56:25.8642963Z  prefixes.append(label) 2025-06-01T20:56:25.8643787Z  2025-06-01T20:56:25.8644371Z  if len(prefixes) > 1: 2025-06-01T20:56:25.8645527Z  log.error( 2025-06-01T20:56:25.8647353Z  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:25.8649253Z  ) 2025-06-01T20:56:25.8649935Z  prefixes = prefixes[:1] 2025-06-01T20:56:25.8650724Z  2025-06-01T20:56:25.8651318Z  # Fleet always comes first 2025-06-01T20:56:25.8652137Z  if fleet_prefix: 2025-06-01T20:56:25.8652949Z  prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:25.8653796Z  2025-06-01T20:56:25.8654525Z  return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:25.8655643Z  2025-06-01T20:56:25.8656213Z  2025-06-01T20:56:25.8657278Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:25.8658576Z  """ 2025-06-01T20:56:25.8659609Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:25.8660858Z  2025-06-01T20:56:25.8661811Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:25.8663024Z  """ 2025-06-01T20:56:25.8663705Z  gh = get_gh_client(github_token) 2025-06-01T20:56:25.8664626Z  issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:25.8665979Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:25.8667003Z  2025-06-01T20:56:25.8667552Z  2025-06-01T20:56:25.8668530Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:25.8670061Z  for _ in range(num_retries): 2025-06-01T20:56:25.8670892Z  try: 2025-06-01T20:56:25.8671621Z  req = Request(url=url, headers=headers) 2025-06-01T20:56:25.8672780Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:25.8673864Z  return json.loads(content) 2025-06-01T20:56:25.8674747Z  except Exception as e: 2025-06-01T20:56:25.8675912Z  log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:25.8676843Z  2025-06-01T20:56:25.8677793Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:25.8679019Z  return {} 2025-06-01T20:56:25.8679670Z  2025-06-01T20:56:25.8680215Z  2025-06-01T20:56:25.8680768Z @cache 2025-06-01T20:56:25.8681865Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:25.8683151Z  """ 2025-06-01T20:56:25.8683822Z  Dynamically get PR information 2025-06-01T20:56:25.8684670Z  """ 2025-06-01T20:56:25.8685751Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:25.8686825Z  headers = { 2025-06-01T20:56:25.8687625Z  "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:25.8688666Z  "Authorization": f"token {github_token}", 2025-06-01T20:56:25.8689573Z  } 2025-06-01T20:56:25.8690304Z  json_response: dict[str, Any] = download_json( 2025-06-01T20:56:25.8691332Z  url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:25.8692269Z  headers=headers, 2025-06-01T20:56:25.8693002Z  ) 2025-06-01T20:56:25.8693585Z  2025-06-01T20:56:25.8694161Z  if not json_response: 2025-06-01T20:56:25.8695204Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:25.8696497Z  return {} 2025-06-01T20:56:25.8697195Z  2025-06-01T20:56:25.8697779Z  return json_response 2025-06-01T20:56:25.8698747Z  2025-06-01T20:56:25.8699310Z  2025-06-01T20:56:25.8700285Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:25.8701497Z  """ 2025-06-01T20:56:25.8702420Z  Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:25.8703510Z  """ 2025-06-01T20:56:25.8704322Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:25.8705615Z  return { 2025-06-01T20:56:25.8706646Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:25.8707787Z  } 2025-06-01T20:56:25.8708385Z  2025-06-01T20:56:25.8708940Z  2025-06-01T20:56:25.8709537Z def main() -> None: 2025-06-01T20:56:25.8710282Z  args = parse_args() 2025-06-01T20:56:25.8711012Z  2025-06-01T20:56:25.8711798Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:25.8712731Z  2025-06-01T20:56:25.8713346Z  # Check if the PR is opt-out 2025-06-01T20:56:25.8714164Z  if args.pr_number: 2025-06-01T20:56:25.8715502Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:25.8716798Z  if OPT_OUT_LABEL in labels: 2025-06-01T20:56:25.8717632Z  log.info( 2025-06-01T20:56:25.8718843Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:25.8720127Z  ) 2025-06-01T20:56:25.8721100Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:25.8722265Z  sys.exit() 2025-06-01T20:56:25.8723188Z  2025-06-01T20:56:25.8723743Z  try: 2025-06-01T20:56:25.8724495Z  rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:25.8725929Z  args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:25.8727018Z  ) 2025-06-01T20:56:25.8727624Z  2025-06-01T20:56:25.8728260Z  username = get_potential_pr_author( 2025-06-01T20:56:25.8729187Z  args.github_token, 2025-06-01T20:56:25.8730014Z  args.github_repo, 2025-06-01T20:56:25.8730833Z  args.github_actor, 2025-06-01T20:56:25.8731674Z  args.github_ref_type, 2025-06-01T20:56:25.8732556Z  args.github_branch, 2025-06-01T20:56:25.8733338Z  ) 2025-06-01T20:56:25.8733943Z  2025-06-01T20:56:25.8734745Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:25.8735972Z  2025-06-01T20:56:25.8736672Z  runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:25.8737587Z  rollout_state, 2025-06-01T20:56:25.8738456Z  (args.github_issue_owner, username), 2025-06-01T20:56:25.8739378Z  args.github_branch, 2025-06-01T20:56:25.8740247Z  args.eligible_experiments, 2025-06-01T20:56:25.8741153Z  args.opt_out_experiments, 2025-06-01T20:56:25.8742010Z  is_canary, 2025-06-01T20:56:25.8742745Z  ) 2025-06-01T20:56:25.8743346Z  2025-06-01T20:56:25.8743934Z  except Exception as e: 2025-06-01T20:56:25.8744713Z  log.error( 2025-06-01T20:56:25.8746164Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:25.8747439Z  ) 2025-06-01T20:56:25.8748061Z  2025-06-01T20:56:25.8748962Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:25.8750090Z  2025-06-01T20:56:25.8750876Z  2025-06-01T20:56:25.8751423Z if __name__ == "__main__": 2025-06-01T20:56:25.8752161Z  main() 2025-06-01T20:56:25.8752774Z  2025-06-01T20:56:25.8753328Z EOF 2025-06-01T20:56:25.8753890Z  2025-06-01T20:56:25.8754492Z cat runner_determinator.py 2025-06-01T20:56:25.9153571Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:25.9154763Z env: 2025-06-01T20:56:25.9155922Z GITHUB_TOKEN: *** 2025-06-01T20:56:25.9156577Z ISSUE_NUMBER: 5132 2025-06-01T20:56:25.9157246Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:25.9158039Z ISSUE_OWNER: 2025-06-01T20:56:25.9158677Z CHECK_EXPERIMENTS: 2025-06-01T20:56:25.9159361Z OPT_OUT_EXPERIMENTS: lf 2025-06-01T20:56:25.9160038Z PR_NUMBER: 2025-06-01T20:56:25.9160671Z ##[endgroup] 2025-06-01T20:56:25.9474013Z # flake8: noqa: G004 2025-06-01T20:56:25.9474515Z 2025-06-01T20:56:25.9475214Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:25.9477063Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:25.9478419Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:25.9479191Z 2025-06-01T20:56:25.9479458Z """ 2025-06-01T20:56:25.9480432Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:25.9481951Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:25.9483477Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:25.9484842Z of which runners should be used to run which job. 2025-06-01T20:56:25.9485761Z 2025-06-01T20:56:25.9486416Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:25.9488311Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:25.9490286Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:25.9491436Z list, defined. 2025-06-01T20:56:25.9491830Z 2025-06-01T20:56:25.9492415Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:25.9493882Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:25.9495259Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:25.9496223Z 2025-06-01T20:56:25.9496849Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:25.9498288Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:25.9499557Z experiments which the user could be opted in to. 2025-06-01T20:56:25.9500239Z 2025-06-01T20:56:25.9500569Z The user list has the following rules: 2025-06-01T20:56:25.9501191Z 2025-06-01T20:56:25.9501732Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:25.9503197Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:25.9504542Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:25.9505240Z 2025-06-01T20:56:25.9505743Z Example config: 2025-06-01T20:56:25.9506518Z # A list of experiments that can be opted into. 2025-06-01T20:56:25.9507665Z # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:25.9508726Z # Expected syntax is: 2025-06-01T20:56:25.9509835Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:25.9511457Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:25.9512493Z 2025-06-01T20:56:25.9512765Z experiments: 2025-06-01T20:56:25.9513377Z lf: 2025-06-01T20:56:25.9513953Z rollout_percent: 25 2025-06-01T20:56:25.9514710Z all_branches: false 2025-06-01T20:56:25.9516386Z default: true 2025-06-01T20:56:25.9517108Z --- 2025-06-01T20:56:25.9517418Z 2025-06-01T20:56:25.9517659Z # Opt-ins: 2025-06-01T20:56:25.9518636Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:25.9520410Z # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:25.9521724Z # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:25.9522864Z # Experiments should be from the above list. 2025-06-01T20:56:25.9523531Z 2025-06-01T20:56:25.9523840Z @User1,-lf,split_build 2025-06-01T20:56:25.9524651Z @User2,lf 2025-06-01T20:56:25.9525497Z @User3,split_build 2025-06-01T20:56:25.9526197Z """ 2025-06-01T20:56:25.9526520Z 2025-06-01T20:56:25.9526783Z import json 2025-06-01T20:56:25.9527379Z import logging 2025-06-01T20:56:25.9527993Z import os 2025-06-01T20:56:25.9528605Z import random 2025-06-01T20:56:25.9529228Z import re 2025-06-01T20:56:25.9529819Z import sys 2025-06-01T20:56:25.9530497Z from argparse import ArgumentParser 2025-06-01T20:56:25.9531360Z from collections.abc import Iterable 2025-06-01T20:56:25.9532213Z from functools import cache 2025-06-01T20:56:25.9533019Z from logging import LogRecord 2025-06-01T20:56:25.9533855Z from typing import Any, NamedTuple 2025-06-01T20:56:25.9534764Z from urllib.request import Request, urlopen 2025-06-01T20:56:25.9535637Z 2025-06-01T20:56:25.9535925Z import yaml 2025-06-01T20:56:25.9536571Z from github import Auth, Github 2025-06-01T20:56:25.9537365Z from github.Issue import Issue 2025-06-01T20:56:25.9537869Z 2025-06-01T20:56:25.9537888Z 2025-06-01T20:56:25.9538248Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:25.9539428Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:25.9540883Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:25.9541817Z 2025-06-01T20:56:25.9542195Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:25.9543430Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:25.9544351Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:25.9545525Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:25.9546179Z 2025-06-01T20:56:25.9546497Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:25.9547058Z 2025-06-01T20:56:25.9547351Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:25.9548135Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:25.9548624Z 2025-06-01T20:56:25.9548635Z 2025-06-01T20:56:25.9548956Z class Experiment(NamedTuple): 2025-06-01T20:56:25.9549760Z rollout_perc: float = ( 2025-06-01T20:56:25.9550819Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:25.9551986Z ) 2025-06-01T20:56:25.9552583Z all_branches: bool = ( 2025-06-01T20:56:25.9553608Z False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:25.9554722Z ) 2025-06-01T20:56:25.9555499Z default: bool = ( 2025-06-01T20:56:25.9556464Z True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:25.9557577Z ) 2025-06-01T20:56:25.9557922Z 2025-06-01T20:56:25.9558245Z # Add more fields as needed 2025-06-01T20:56:25.9558770Z 2025-06-01T20:56:25.9558780Z 2025-06-01T20:56:25.9559106Z class Settings(NamedTuple): 2025-06-01T20:56:25.9559843Z """ 2025-06-01T20:56:25.9560585Z Settings for the experiments that can be opted into. 2025-06-01T20:56:25.9561512Z """ 2025-06-01T20:56:25.9561840Z 2025-06-01T20:56:25.9562166Z experiments: dict[str, Experiment] = {} 2025-06-01T20:56:25.9562775Z 2025-06-01T20:56:25.9562787Z 2025-06-01T20:56:25.9563144Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:25.9564204Z """Color codes the log messages based on the log level""" 2025-06-01T20:56:25.9564946Z 2025-06-01T20:56:25.9565231Z COLORS = { 2025-06-01T20:56:25.9566136Z "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:25.9567000Z "ERROR": "\033[31m", # Red 2025-06-01T20:56:25.9567850Z "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:25.9568704Z "INFO": "\033[0m", # Reset 2025-06-01T20:56:25.9569482Z "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:25.9570494Z } 2025-06-01T20:56:25.9570808Z 2025-06-01T20:56:25.9571155Z def format(self, record: LogRecord) -> str: 2025-06-01T20:56:25.9572384Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:25.9573709Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:25.9574711Z return super().format(record) 2025-06-01T20:56:25.9575509Z 2025-06-01T20:56:25.9575521Z 2025-06-01T20:56:25.9575859Z handler = logging.StreamHandler() 2025-06-01T20:56:25.9577072Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:25.9578074Z 2025-06-01T20:56:25.9578500Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:25.9579485Z log.addHandler(handler) 2025-06-01T20:56:25.9580212Z log.setLevel(logging.INFO) 2025-06-01T20:56:25.9580679Z 2025-06-01T20:56:25.9580688Z 2025-06-01T20:56:25.9581087Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:25.9582048Z """ 2025-06-01T20:56:25.9582894Z Defines outputs of the github action that invokes this script 2025-06-01T20:56:25.9583932Z """ 2025-06-01T20:56:25.9584505Z if not GITHUB_OUTPUT: 2025-06-01T20:56:25.9586315Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:25.9587403Z log.warning( 2025-06-01T20:56:25.9588223Z "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:25.9589072Z ) 2025-06-01T20:56:25.9598858Z print(f"::set-output name={key}::{value}") 2025-06-01T20:56:25.9599407Z return 2025-06-01T20:56:25.9599627Z 2025-06-01T20:56:25.9600009Z with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:25.9600556Z log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:25.9601076Z f.write(f"{key}={value}\n") 2025-06-01T20:56:25.9601376Z 2025-06-01T20:56:25.9601389Z 2025-06-01T20:56:25.9601664Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:25.9602238Z return frozenset( 2025-06-01T20:56:25.9602800Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:25.9603466Z ) 2025-06-01T20:56:25.9603646Z 2025-06-01T20:56:25.9603653Z 2025-06-01T20:56:25.9603816Z def parse_args() -> Any: 2025-06-01T20:56:25.9604331Z parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:25.9605123Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:25.9606055Z parser.add_argument( 2025-06-01T20:56:25.9606461Z "--github-issue-repo", 2025-06-01T20:56:25.9606879Z type=str, 2025-06-01T20:56:25.9607240Z required=False, 2025-06-01T20:56:25.9607652Z default="pytorch/test-infra", 2025-06-01T20:56:25.9608142Z help="GitHub repo to get the issue", 2025-06-01T20:56:25.9608601Z ) 2025-06-01T20:56:25.9608934Z parser.add_argument( 2025-06-01T20:56:25.9609336Z "--github-repo", 2025-06-01T20:56:25.9609731Z type=str, 2025-06-01T20:56:25.9610079Z required=True, 2025-06-01T20:56:25.9610494Z help="GitHub repo where CI is running", 2025-06-01T20:56:25.9610960Z ) 2025-06-01T20:56:25.9611297Z parser.add_argument( 2025-06-01T20:56:25.9611838Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:25.9612443Z ) 2025-06-01T20:56:25.9612766Z parser.add_argument( 2025-06-01T20:56:25.9613329Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:25.9613948Z ) 2025-06-01T20:56:25.9614257Z parser.add_argument( 2025-06-01T20:56:25.9614839Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:25.9615658Z ) 2025-06-01T20:56:25.9615983Z parser.add_argument( 2025-06-01T20:56:25.9616713Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:25.9617364Z ) 2025-06-01T20:56:25.9617684Z parser.add_argument( 2025-06-01T20:56:25.9618099Z "--github-ref-type", 2025-06-01T20:56:25.9618508Z type=str, 2025-06-01T20:56:25.9618856Z required=True, 2025-06-01T20:56:25.9619301Z help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:25.9619790Z ) 2025-06-01T20:56:25.9620119Z parser.add_argument( 2025-06-01T20:56:25.9620532Z "--eligible-experiments", 2025-06-01T20:56:25.9621000Z type=_str_comma_separated_to_set, 2025-06-01T20:56:25.9621464Z required=False, 2025-06-01T20:56:25.9621830Z default="", 2025-06-01T20:56:25.9622657Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:25.9623574Z ) 2025-06-01T20:56:25.9623951Z parser.add_argument( 2025-06-01T20:56:25.9624376Z "--opt-out-experiments", 2025-06-01T20:56:25.9624837Z type=_str_comma_separated_to_set, 2025-06-01T20:56:25.9625476Z required=False, 2025-06-01T20:56:25.9625892Z default="", 2025-06-01T20:56:25.9626237Z help=( 2025-06-01T20:56:25.9626852Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:25.9627886Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:25.9628649Z ), 2025-06-01T20:56:25.9628965Z ) 2025-06-01T20:56:25.9629291Z parser.add_argument( 2025-06-01T20:56:25.9629683Z "--pr-number", 2025-06-01T20:56:25.9630048Z type=str, 2025-06-01T20:56:25.9630407Z required=False, 2025-06-01T20:56:25.9630779Z default="", 2025-06-01T20:56:25.9631328Z help="the optional PR number where this is run", 2025-06-01T20:56:25.9631830Z ) 2025-06-01T20:56:25.9632011Z 2025-06-01T20:56:25.9632185Z return parser.parse_args() 2025-06-01T20:56:25.9632458Z 2025-06-01T20:56:25.9632464Z 2025-06-01T20:56:25.9632833Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:25.9633513Z auth = Auth.Token(github_token) 2025-06-01T20:56:25.9633966Z return Github(auth=auth) 2025-06-01T20:56:25.9634228Z 2025-06-01T20:56:25.9634234Z 2025-06-01T20:56:25.9634645Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:25.9635527Z repo = gh.get_repo(repo) 2025-06-01T20:56:25.9635983Z return repo.get_issue(number=issue_num) 2025-06-01T20:56:25.9636314Z 2025-06-01T20:56:25.9636320Z 2025-06-01T20:56:25.9636489Z def get_potential_pr_author( 2025-06-01T20:56:25.9637076Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:25.9637691Z ) -> str: 2025-06-01T20:56:25.9638156Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:25.9638891Z # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:25.9639561Z # embedded in the tag name: ciflow// 2025-06-01T20:56:25.9639942Z 2025-06-01T20:56:25.9640109Z gh = get_gh_client(github_token) 2025-06-01T20:56:25.9640414Z 2025-06-01T20:56:25.9640650Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:25.9641213Z split_tag = ref_name.split("/") 2025-06-01T20:56:25.9641657Z if ( 2025-06-01T20:56:25.9641998Z len(split_tag) == 3 2025-06-01T20:56:25.9642425Z and split_tag[0] == "ciflow" 2025-06-01T20:56:25.9642898Z and split_tag[2].isnumeric() 2025-06-01T20:56:25.9643381Z ): 2025-06-01T20:56:25.9643727Z pr_number = split_tag[2] 2025-06-01T20:56:25.9644153Z try: 2025-06-01T20:56:25.9644538Z repository = gh.get_repo(repo) 2025-06-01T20:56:25.9645085Z pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:25.9645855Z except Exception as e: 2025-06-01T20:56:25.9646330Z raise Exception( # noqa: TRY002 2025-06-01T20:56:25.9646922Z f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:25.9647502Z ) from e 2025-06-01T20:56:25.9647972Z return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:25.9648603Z # In all other cases, return the original input username 2025-06-01T20:56:25.9649127Z return username 2025-06-01T20:56:25.9649348Z 2025-06-01T20:56:25.9649353Z 2025-06-01T20:56:25.9649552Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:25.9650030Z """ 2025-06-01T20:56:25.9650602Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:25.9651309Z """ 2025-06-01T20:56:25.9651795Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:25.9652272Z 2025-06-01T20:56:25.9652283Z 2025-06-01T20:56:25.9652459Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:25.9652897Z try: 2025-06-01T20:56:25.9653250Z data = yaml.safe_load(yaml_text) 2025-06-01T20:56:25.9653711Z return data 2025-06-01T20:56:25.9654075Z except yaml.YAMLError: 2025-06-01T20:56:25.9654509Z log.exception("Error loading YAML") 2025-06-01T20:56:25.9654964Z raise 2025-06-01T20:56:25.9655155Z 2025-06-01T20:56:25.9655166Z 2025-06-01T20:56:25.9655642Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:25.9656315Z """ 2025-06-01T20:56:25.9656870Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:25.9657422Z 2025-06-01T20:56:25.9657859Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:25.9658542Z and the text below is the list of opted in users. 2025-06-01T20:56:25.9658907Z 2025-06-01T20:56:25.9659257Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:25.9659885Z """ 2025-06-01T20:56:25.9660278Z rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:25.9660802Z if len(rollout_state_parts) >= 2: 2025-06-01T20:56:25.9661341Z return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:25.9661870Z else: 2025-06-01T20:56:25.9662205Z return "", rollout_state 2025-06-01T20:56:25.9662485Z 2025-06-01T20:56:25.9662492Z 2025-06-01T20:56:25.9662677Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:25.9663136Z """ 2025-06-01T20:56:25.9710344Z Dictionary of users with a list of features they have opted into 2025-06-01T20:56:25.9711521Z """ 2025-06-01T20:56:25.9711890Z 2025-06-01T20:56:25.9711900Z 2025-06-01T20:56:25.9712368Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:25.9713023Z """ 2025-06-01T20:56:25.9713706Z 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:25.9714355Z 2025-06-01T20:56:25.9714941Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:25.9716087Z - Example line: "@User1,lf,split_build" 2025-06-01T20:56:25.9716730Z - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:25.9717173Z 2025-06-01T20:56:25.9717180Z 2025-06-01T20:56:25.9717324Z """ 2025-06-01T20:56:25.9717666Z optins = UserOptins() 2025-06-01T20:56:25.9718115Z for user in user_optin_text.split("\n"): 2025-06-01T20:56:25.9718624Z user = user.strip("\r\n\t -") 2025-06-01T20:56:25.9719132Z if not user or not user.startswith("@"): 2025-06-01T20:56:25.9719637Z # Not a valid user. Skip 2025-06-01T20:56:25.9720080Z continue 2025-06-01T20:56:25.9720302Z 2025-06-01T20:56:25.9720445Z if user: 2025-06-01T20:56:25.9721036Z usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:25.9721668Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:25.9722132Z 2025-06-01T20:56:25.9722280Z return optins 2025-06-01T20:56:25.9722491Z 2025-06-01T20:56:25.9722497Z 2025-06-01T20:56:25.9722766Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:25.9723323Z """ 2025-06-01T20:56:25.9723693Z Check if the experiment name is valid. 2025-06-01T20:56:25.9724166Z A valid name: 2025-06-01T20:56:25.9724754Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:25.9725807Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:25.9726519Z - Cannot contain spaces 2025-06-01T20:56:25.9726997Z """ 2025-06-01T20:56:25.9727182Z 2025-06-01T20:56:25.9727424Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:25.9728065Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:25.9728469Z 2025-06-01T20:56:25.9728616Z if valid: 2025-06-01T20:56:25.9728956Z return True 2025-06-01T20:56:25.9729168Z 2025-06-01T20:56:25.9729311Z log.error( 2025-06-01T20:56:25.9730644Z 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:25.9732107Z ) 2025-06-01T20:56:25.9732426Z return False 2025-06-01T20:56:25.9732640Z 2025-06-01T20:56:25.9732646Z 2025-06-01T20:56:25.9732915Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:25.9733489Z """ 2025-06-01T20:56:25.9734157Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:25.9734814Z """ 2025-06-01T20:56:25.9735123Z try: 2025-06-01T20:56:25.9735645Z if settings_text: 2025-06-01T20:56:25.9736325Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:25.9737054Z # for easy reading 2025-06-01T20:56:25.9737768Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:25.9738579Z # the backtick character in shell commands. 2025-06-01T20:56:25.9739128Z backtick = chr(96) # backtick character 2025-06-01T20:56:25.9739724Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:25.9740323Z settings = load_yaml(settings_text) 2025-06-01T20:56:25.9740656Z 2025-06-01T20:56:25.9741025Z # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:25.9741714Z experiments = {} 2025-06-01T20:56:25.9741982Z 2025-06-01T20:56:25.9742333Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:25.9743020Z if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:25.9744076Z # 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:25.9745030Z continue 2025-06-01T20:56:25.9745482Z 2025-06-01T20:56:25.9745656Z valid_settings = {} 2025-06-01T20:56:25.9746138Z for setting in exp_settings: 2025-06-01T20:56:25.9746657Z if setting not in Experiment._fields: 2025-06-01T20:56:25.9747167Z log.warning( 2025-06-01T20:56:25.9747794Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:25.9748447Z ) 2025-06-01T20:56:25.9748831Z else: 2025-06-01T20:56:25.9749312Z valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:25.9749833Z 2025-06-01T20:56:25.9750077Z experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:25.9750660Z return Settings(experiments) 2025-06-01T20:56:25.9750972Z 2025-06-01T20:56:25.9751132Z except Exception: 2025-06-01T20:56:25.9751553Z log.exception("Failed to parse settings") 2025-06-01T20:56:25.9751908Z 2025-06-01T20:56:25.9752066Z return Settings() 2025-06-01T20:56:25.9752292Z 2025-06-01T20:56:25.9752297Z 2025-06-01T20:56:25.9752516Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:25.9753029Z """ 2025-06-01T20:56:25.9753413Z Parse settings, if any, from the rollout state. 2025-06-01T20:56:25.9753792Z 2025-06-01T20:56:25.9754111Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:25.9754810Z and the text below is the list of opted in users. 2025-06-01T20:56:25.9755180Z 2025-06-01T20:56:25.9755762Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:25.9756444Z """ 2025-06-01T20:56:25.9756936Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:25.9757625Z return parse_settings_from_text(settings_text) 2025-06-01T20:56:25.9757984Z 2025-06-01T20:56:25.9757990Z 2025-06-01T20:56:25.9758213Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:25.9758706Z """ 2025-06-01T20:56:25.9759055Z Parse users from the rollout state. 2025-06-01T20:56:25.9759372Z 2025-06-01T20:56:25.9759522Z """ 2025-06-01T20:56:25.9759994Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:25.9760649Z return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:25.9761014Z 2025-06-01T20:56:25.9761021Z 2025-06-01T20:56:25.9761521Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:25.9762201Z """ 2025-06-01T20:56:25.9762564Z Check if a user is opted into an experiment 2025-06-01T20:56:25.9763046Z """ 2025-06-01T20:56:25.9763445Z return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:25.9763828Z 2025-06-01T20:56:25.9763834Z 2025-06-01T20:56:25.9764211Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:25.9764877Z """ 2025-06-01T20:56:25.9765432Z Check if a user explicitly opted out of an experiment 2025-06-01T20:56:25.9766002Z """ 2025-06-01T20:56:25.9766449Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:25.9767067Z experiment_optout = "-" + experiment_name 2025-06-01T20:56:25.9767639Z if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:25.9768177Z return False 2025-06-01T20:56:25.9768399Z 2025-06-01T20:56:25.9768641Z if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:25.9769180Z log.warning( 2025-06-01T20:56:25.9769911Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:25.9770702Z ) 2025-06-01T20:56:25.9770882Z 2025-06-01T20:56:25.9771031Z return True 2025-06-01T20:56:25.9771236Z 2025-06-01T20:56:25.9771241Z 2025-06-01T20:56:25.9771396Z def get_runner_prefix( 2025-06-01T20:56:25.9771779Z rollout_state: str, 2025-06-01T20:56:25.9772183Z workflow_requestors: Iterable[str], 2025-06-01T20:56:25.9772640Z branch: str, 2025-06-01T20:56:25.9773070Z eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:25.9773671Z opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:25.9774196Z is_canary: bool = False, 2025-06-01T20:56:25.9774596Z ) -> str: 2025-06-01T20:56:25.9774967Z settings = parse_settings(rollout_state) 2025-06-01T20:56:25.9775651Z user_optins = parse_users(rollout_state) 2025-06-01T20:56:25.9775987Z 2025-06-01T20:56:25.9776138Z fleet_prefix = "" 2025-06-01T20:56:25.9776641Z prefixes = [] 2025-06-01T20:56:25.9777197Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:25.9778039Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:25.9778685Z log.info( 2025-06-01T20:56:25.9779299Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:25.9779976Z ) 2025-06-01T20:56:25.9780311Z continue 2025-06-01T20:56:25.9780533Z 2025-06-01T20:56:25.9780700Z if opt_out_experiments: 2025-06-01T20:56:25.9781175Z if experiment_name in opt_out_experiments: 2025-06-01T20:56:25.9781749Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:25.9782274Z log.info( 2025-06-01T20:56:25.9783105Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:25.9783993Z ) 2025-06-01T20:56:25.9784343Z continue 2025-06-01T20:56:25.9784580Z 2025-06-01T20:56:25.9784743Z if eligible_experiments: 2025-06-01T20:56:25.9785236Z if experiment_name not in eligible_experiments: 2025-06-01T20:56:25.9786013Z exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:25.9786516Z log.info( 2025-06-01T20:56:25.9787210Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:25.9787976Z ) 2025-06-01T20:56:25.9788325Z continue 2025-06-01T20:56:25.9788741Z elif not experiment_settings.default: 2025-06-01T20:56:25.9789223Z log.info( 2025-06-01T20:56:25.9789990Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:25.9790664Z ) 2025-06-01T20:56:25.9790994Z continue 2025-06-01T20:56:25.9791228Z 2025-06-01T20:56:25.9791472Z # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:25.9792033Z opted_out_users = [ 2025-06-01T20:56:25.9792424Z requestor 2025-06-01T20:56:25.9792826Z for requestor in workflow_requestors 2025-06-01T20:56:25.9793429Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:25.9793996Z ] 2025-06-01T20:56:25.9794173Z 2025-06-01T20:56:25.9794331Z if opted_out_users: 2025-06-01T20:56:25.9794730Z log.info( 2025-06-01T20:56:25.9795468Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:25.9796124Z ) 2025-06-01T20:56:25.9796452Z continue 2025-06-01T20:56:25.9796668Z 2025-06-01T20:56:25.9796914Z # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:25.9797458Z opted_in_users = [ 2025-06-01T20:56:25.9797843Z requestor 2025-06-01T20:56:25.9798255Z for requestor in workflow_requestors 2025-06-01T20:56:25.9798844Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:25.9799406Z ] 2025-06-01T20:56:25.9799582Z 2025-06-01T20:56:25.9799731Z enabled = False 2025-06-01T20:56:25.9800114Z if opted_in_users: 2025-06-01T20:56:25.9800504Z log.info( 2025-06-01T20:56:25.9801039Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:25.9801656Z ) 2025-06-01T20:56:25.9801989Z enabled = True 2025-06-01T20:56:25.9802242Z 2025-06-01T20:56:25.9802432Z elif experiment_settings.rollout_perc: 2025-06-01T20:56:25.9803177Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:25.9804021Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:25.9804606Z log.info( 2025-06-01T20:56:25.9806009Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:25.9806865Z ) 2025-06-01T20:56:25.9807216Z enabled = True 2025-06-01T20:56:25.9807496Z 2025-06-01T20:56:25.9807637Z if enabled: 2025-06-01T20:56:25.9808013Z label = experiment_name 2025-06-01T20:56:25.9808511Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:25.9809251Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:25.9810047Z # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:25.9810738Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:25.9811335Z if is_canary: 2025-06-01T20:56:25.9811774Z label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:25.9812271Z fleet_prefix = label 2025-06-01T20:56:25.9812708Z else: 2025-06-01T20:56:25.9813090Z prefixes.append(label) 2025-06-01T20:56:25.9813408Z 2025-06-01T20:56:25.9813574Z if len(prefixes) > 1: 2025-06-01T20:56:25.9813962Z log.error( 2025-06-01T20:56:25.9814890Z 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:25.9816078Z ) 2025-06-01T20:56:25.9816414Z prefixes = prefixes[:1] 2025-06-01T20:56:25.9816704Z 2025-06-01T20:56:25.9816870Z # Fleet always comes first 2025-06-01T20:56:25.9817285Z if fleet_prefix: 2025-06-01T20:56:25.9817691Z prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:25.9818011Z 2025-06-01T20:56:25.9818372Z return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:25.9818751Z 2025-06-01T20:56:25.9818757Z 2025-06-01T20:56:25.9819159Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:25.9819867Z """ 2025-06-01T20:56:25.9820395Z Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:25.9820910Z 2025-06-01T20:56:25.9821250Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:25.9821884Z """ 2025-06-01T20:56:25.9822219Z gh = get_gh_client(github_token) 2025-06-01T20:56:25.9822697Z issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:25.9823268Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:25.9823672Z 2025-06-01T20:56:25.9823677Z 2025-06-01T20:56:25.9824029Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:25.9824709Z for _ in range(num_retries): 2025-06-01T20:56:25.9825140Z try: 2025-06-01T20:56:25.9825724Z req = Request(url=url, headers=headers) 2025-06-01T20:56:25.9826316Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:25.9826900Z return json.loads(content) 2025-06-01T20:56:25.9827366Z except Exception as e: 2025-06-01T20:56:25.9827841Z log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:25.9828209Z 2025-06-01T20:56:25.9828545Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:25.9829175Z return {} 2025-06-01T20:56:25.9829366Z 2025-06-01T20:56:25.9829373Z 2025-06-01T20:56:25.9829515Z @cache 2025-06-01T20:56:25.9830059Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:25.9830740Z """ 2025-06-01T20:56:25.9831083Z Dynamically get PR information 2025-06-01T20:56:25.9831510Z """ 2025-06-01T20:56:25.9831958Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:25.9832526Z headers = { 2025-06-01T20:56:25.9832924Z "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:25.9833601Z "Authorization": f"token {github_token}", 2025-06-01T20:56:25.9834072Z } 2025-06-01T20:56:25.9834444Z json_response: dict[str, Any] = download_json( 2025-06-01T20:56:25.9834990Z url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:25.9835684Z headers=headers, 2025-06-01T20:56:25.9836066Z ) 2025-06-01T20:56:25.9836242Z 2025-06-01T20:56:25.9836401Z if not json_response: 2025-06-01T20:56:25.9836906Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:25.9837451Z return {} 2025-06-01T20:56:25.9837663Z 2025-06-01T20:56:25.9837819Z return json_response 2025-06-01T20:56:25.9838069Z 2025-06-01T20:56:25.9838075Z 2025-06-01T20:56:25.9838436Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:25.9839089Z """ 2025-06-01T20:56:25.9839556Z Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:25.9840137Z """ 2025-06-01T20:56:25.9840568Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:25.9841103Z return { 2025-06-01T20:56:25.9841626Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:25.9842259Z } 2025-06-01T20:56:25.9842443Z 2025-06-01T20:56:25.9842449Z 2025-06-01T20:56:25.9842600Z def main() -> None: 2025-06-01T20:56:25.9842972Z args = parse_args() 2025-06-01T20:56:25.9843241Z 2025-06-01T20:56:25.9843435Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:25.9843785Z 2025-06-01T20:56:25.9843950Z # Check if the PR is opt-out 2025-06-01T20:56:25.9844382Z if args.pr_number: 2025-06-01T20:56:25.9844963Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:25.9845913Z if OPT_OUT_LABEL in labels: 2025-06-01T20:56:25.9846371Z log.info( 2025-06-01T20:56:25.9846988Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:25.9847678Z ) 2025-06-01T20:56:25.9848160Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:25.9848744Z sys.exit() 2025-06-01T20:56:25.9848990Z 2025-06-01T20:56:25.9849128Z try: 2025-06-01T20:56:25.9849510Z rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:25.9850143Z args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:25.9850708Z ) 2025-06-01T20:56:25.9850890Z 2025-06-01T20:56:25.9851069Z username = get_potential_pr_author( 2025-06-01T20:56:25.9851555Z args.github_token, 2025-06-01T20:56:25.9851976Z args.github_repo, 2025-06-01T20:56:25.9852396Z args.github_actor, 2025-06-01T20:56:25.9852815Z args.github_ref_type, 2025-06-01T20:56:25.9853254Z args.github_branch, 2025-06-01T20:56:25.9853663Z ) 2025-06-01T20:56:25.9853848Z 2025-06-01T20:56:25.9854097Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:25.9854513Z 2025-06-01T20:56:25.9854705Z runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:25.9855194Z rollout_state, 2025-06-01T20:56:25.9855785Z (args.github_issue_owner, username), 2025-06-01T20:56:25.9856265Z args.github_branch, 2025-06-01T20:56:25.9856704Z args.eligible_experiments, 2025-06-01T20:56:25.9857173Z args.opt_out_experiments, 2025-06-01T20:56:25.9857622Z is_canary, 2025-06-01T20:56:25.9857981Z ) 2025-06-01T20:56:25.9858169Z 2025-06-01T20:56:25.9858329Z except Exception as e: 2025-06-01T20:56:25.9858723Z log.error( 2025-06-01T20:56:25.9859319Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:25.9860015Z ) 2025-06-01T20:56:25.9860201Z 2025-06-01T20:56:25.9860489Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:25.9861066Z 2025-06-01T20:56:25.9861072Z 2025-06-01T20:56:25.9861224Z if __name__ == "__main__": 2025-06-01T20:56:25.9861611Z main() 2025-06-01T20:56:25.9861796Z 2025-06-01T20:56:25.9948292Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:25.9949086Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:25.9998565Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:25.9998997Z env: 2025-06-01T20:56:25.9999539Z GITHUB_TOKEN: *** 2025-06-01T20:56:25.9999909Z ISSUE_NUMBER: 5132 2025-06-01T20:56:26.0000313Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:26.0000766Z ISSUE_OWNER: 2025-06-01T20:56:26.0001121Z CHECK_EXPERIMENTS: 2025-06-01T20:56:26.0001505Z OPT_OUT_EXPERIMENTS: lf 2025-06-01T20:56:26.0001896Z PR_NUMBER: 2025-06-01T20:56:26.0002226Z ##[endgroup] 2025-06-01T20:56:27.3153488Z Defaulting to user installation because normal site-packages is not writeable 2025-06-01T20:56:28.0540560Z Collecting urllib3==1.26.18 2025-06-01T20:56:28.0857683Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-06-01T20:56:28.1099020Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.3 MB/s eta 0:00:00 2025-06-01T20:56:28.1289558Z Collecting PyGithub==2.3.0 2025-06-01T20:56:28.1329782Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-06-01T20:56:28.1772434Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-06-01T20:56:28.1803481Z 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.1862193Z 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.1886407Z 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.1910347Z 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.2183147Z Collecting Deprecated (from PyGithub==2.3.0) 2025-06-01T20:56:28.2212173Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-06-01T20:56:28.2433452Z 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.3466461Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:28.3498576Z 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.4500227Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-06-01T20:56:28.4530749Z 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.4701160Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:28.4728803Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-06-01T20:56:28.4943485Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-06-01T20:56:28.5006118Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 30.1 MB/s eta 0:00:00 2025-06-01T20:56:28.5053388Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-06-01T20:56:28.5109787Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 87.2 MB/s eta 0:00:00 2025-06-01T20:56:28.5137891Z 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.5226446Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 121.9 MB/s eta 0:00:00 2025-06-01T20:56:28.5254639Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-06-01T20:56:28.5303150Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-06-01T20:56:28.5368304Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 95.1 MB/s eta 0:00:00 2025-06-01T20:56:28.5396974Z 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:28.5436012Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 34.0 MB/s eta 0:00:00 2025-06-01T20:56:28.5460465Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-06-01T20:56:28.5501513Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 42.4 MB/s eta 0:00:00 2025-06-01T20:56:28.8443913Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-06-01T20:56:29.3650077Z 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.4360641Z ##[group]Run curr_branch="main" 2025-06-01T20:56:29.4360937Z curr_branch="main" 2025-06-01T20:56:29.4361154Z curr_ref_type="branch" 2025-06-01T20:56:29.4361399Z echo "Current branch is '$curr_branch'" 2025-06-01T20:56:29.4361649Z  2025-06-01T20:56:29.4361833Z python3 runner_determinator.py \ 2025-06-01T20:56:29.4362139Z  --github-token "$GITHUB_TOKEN" \ 2025-06-01T20:56:29.4362405Z  --github-issue "$ISSUE_NUMBER" \ 2025-06-01T20:56:29.4362655Z  --github-branch "$curr_branch" \ 2025-06-01T20:56:29.4362911Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-06-01T20:56:29.4363185Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-06-01T20:56:29.4363484Z  --github-ref-type "$curr_ref_type" \ 2025-06-01T20:56:29.4363751Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-06-01T20:56:29.4364056Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-06-01T20:56:29.4364406Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-06-01T20:56:29.4364741Z  --pr-number "${PR_NUMBER}" 2025-06-01T20:56:29.4416333Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:29.4416559Z env: 2025-06-01T20:56:29.4417020Z GITHUB_TOKEN: *** 2025-06-01T20:56:29.4417207Z ISSUE_NUMBER: 5132 2025-06-01T20:56:29.4417423Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:29.4417649Z ISSUE_OWNER: 2025-06-01T20:56:29.4417832Z CHECK_EXPERIMENTS: 2025-06-01T20:56:29.4418020Z OPT_OUT_EXPERIMENTS: lf 2025-06-01T20:56:29.4418221Z PR_NUMBER: 2025-06-01T20:56:29.4418379Z ##[endgroup] 2025-06-01T20:56:29.4486275Z Current branch is 'main' 2025-06-01T20:56:31.1752336Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-06-01T20:56:31.1754076Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-06-01T20:56:31.1755744Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-06-01T20:56:31.1756770Z INFO : Setting output: label-type='' 2025-06-01T20:56:31.2054842Z Evaluate and set job outputs 2025-06-01T20:56:31.2061794Z Cleaning up orphan processes