2025-08-26T19:31:40.7410767Z Current runner version: '2.328.0' 2025-08-26T19:31:40.7435614Z ##[group]Runner Image Provisioner 2025-08-26T19:31:40.7436429Z Hosted Compute Agent 2025-08-26T19:31:40.7437015Z Version: 20250818.377 2025-08-26T19:31:40.7437644Z Commit: 3c593e9f75fe0b87e893bca80d6e12ba089c61fc 2025-08-26T19:31:40.7438335Z Build Date: 2025-08-18T14:52:18Z 2025-08-26T19:31:40.7438984Z ##[endgroup] 2025-08-26T19:31:40.7439535Z ##[group]Operating System 2025-08-26T19:31:40.7440138Z Ubuntu 2025-08-26T19:31:40.7440658Z 24.04.2 2025-08-26T19:31:40.7441106Z LTS 2025-08-26T19:31:40.7441564Z ##[endgroup] 2025-08-26T19:31:40.7442000Z ##[group]Runner Image 2025-08-26T19:31:40.7442833Z Image: ubuntu-24.04 2025-08-26T19:31:40.7443325Z Version: 20250818.1.0 2025-08-26T19:31:40.7444301Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250818.1/images/ubuntu/Ubuntu2404-Readme.md 2025-08-26T19:31:40.7445877Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250818.1 2025-08-26T19:31:40.7446912Z ##[endgroup] 2025-08-26T19:31:40.7447987Z ##[group]GITHUB_TOKEN Permissions 2025-08-26T19:31:40.7450374Z Contents: read 2025-08-26T19:31:40.7451230Z Metadata: read 2025-08-26T19:31:40.7451707Z Packages: read 2025-08-26T19:31:40.7452378Z ##[endgroup] 2025-08-26T19:31:40.7454623Z Secret source: Actions 2025-08-26T19:31:40.7455590Z Prepare workflow directory 2025-08-26T19:31:40.7986533Z Prepare all required actions 2025-08-26T19:31:40.8045760Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (262640fd220236042fbf4443cc163c8838c84c3d) 2025-08-26T19:31:40.8050810Z ##[group] Inputs 2025-08-26T19:31:40.8051430Z check_experiments: 2025-08-26T19:31:40.8051980Z opt_out_experiments: 2025-08-26T19:31:40.8052859Z triggering_actor: pytorchmergebot 2025-08-26T19:31:40.8053444Z issue_owner: 2025-08-26T19:31:40.8053935Z curr_branch: main 2025-08-26T19:31:40.8054681Z curr_ref_type: branch 2025-08-26T19:31:40.8055205Z issue_number: 5132 2025-08-26T19:31:40.8055840Z ##[endgroup] 2025-08-26T19:31:40.8056437Z Complete job name: get-label-type / runner-determinator 2025-08-26T19:31:40.8648281Z ##[group]Run cat < runner_determinator.py 2025-08-26T19:31:40.8650579Z cat < runner_determinator.py 2025-08-26T19:31:40.8651260Z # flake8: noqa: G004 2025-08-26T19:31:40.8651760Z  2025-08-26T19:31:40.8652863Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:31:40.8653921Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:31:40.8654879Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:31:40.8655617Z  2025-08-26T19:31:40.8656040Z """ 2025-08-26T19:31:40.8656742Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:31:40.8657723Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:31:40.8658903Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:31:40.8659783Z of which runners should be used to run which job. 2025-08-26T19:31:40.8660491Z  2025-08-26T19:31:40.8661122Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:31:40.8662354Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:31:40.8663506Z settings are considered to be empty with only the second part, the user 2025-08-26T19:31:40.8664266Z list, defined. 2025-08-26T19:31:40.8664777Z  2025-08-26T19:31:40.8665459Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:31:40.8666493Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:31:40.8667462Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:31:40.8668384Z  2025-08-26T19:31:40.8669084Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:31:40.8670004Z The user list is also a comma separated list of additional features or 2025-08-26T19:31:40.8670933Z experiments which the user could be opted in to. 2025-08-26T19:31:40.8671575Z  2025-08-26T19:31:40.8672005Z The user list has the following rules: 2025-08-26T19:31:40.8672811Z  2025-08-26T19:31:40.8673415Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:31:40.8674391Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:31:40.8675322Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:31:40.8675919Z  2025-08-26T19:31:40.8676395Z Example config: 2025-08-26T19:31:40.8676967Z  # A list of experiments that can be opted into. 2025-08-26T19:31:40.8677800Z  # This defines the behavior they'll induce when opted into. 2025-08-26T19:31:40.8678472Z  # Expected syntax is: 2025-08-26T19:31:40.8679304Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:31:40.8680362Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:31:40.8681136Z  2025-08-26T19:31:40.8681641Z  experiments: 2025-08-26T19:31:40.8682378Z  lf: 2025-08-26T19:31:40.8682880Z  rollout_percent: 25 2025-08-26T19:31:40.8683551Z  all_branches: false 2025-08-26T19:31:40.8684087Z  default: true 2025-08-26T19:31:40.8684587Z  --- 2025-08-26T19:31:40.8685066Z  2025-08-26T19:31:40.8685536Z  # Opt-ins: 2025-08-26T19:31:40.8686194Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:31:40.8687417Z  # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:31:40.8688319Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:31:40.8689045Z  # Experiments should be from the above list. 2025-08-26T19:31:40.8689777Z  2025-08-26T19:31:40.8690198Z  @User1,-lf,split_build 2025-08-26T19:31:40.8690743Z  @User2,lf 2025-08-26T19:31:40.8691293Z  @User3,split_build 2025-08-26T19:31:40.8691832Z """ 2025-08-26T19:31:40.8692379Z  2025-08-26T19:31:40.8692921Z import json 2025-08-26T19:31:40.8693422Z import logging 2025-08-26T19:31:40.8693962Z import os 2025-08-26T19:31:40.8694512Z import random 2025-08-26T19:31:40.8695019Z import re 2025-08-26T19:31:40.8695453Z import sys 2025-08-26T19:31:40.8696078Z from argparse import ArgumentParser 2025-08-26T19:31:40.8696723Z from collections.abc import Iterable 2025-08-26T19:31:40.8697361Z from functools import cache 2025-08-26T19:31:40.8697987Z from logging import LogRecord 2025-08-26T19:31:40.8698606Z from typing import Any, NamedTuple 2025-08-26T19:31:40.8699252Z from urllib.request import Request, urlopen 2025-08-26T19:31:40.8699937Z  2025-08-26T19:31:40.8700385Z import yaml 2025-08-26T19:31:40.8700866Z from github import Auth, Github 2025-08-26T19:31:40.8701512Z from github.Issue import Issue 2025-08-26T19:31:40.8702270Z  2025-08-26T19:31:40.8702817Z  2025-08-26T19:31:40.8703370Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:31:40.8704233Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:31:40.8705193Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:31:40.8706245Z  2025-08-26T19:31:40.8706809Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:31:40.8707461Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:31:40.8708185Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:31:40.8708863Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:31:40.8709433Z  2025-08-26T19:31:40.8709998Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:31:40.8710550Z  2025-08-26T19:31:40.8710999Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:31:40.8711599Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:31:40.8712401Z  2025-08-26T19:31:40.8712841Z  2025-08-26T19:31:40.8713316Z class Experiment(NamedTuple): 2025-08-26T19:31:40.8713941Z  rollout_perc: float = ( 2025-08-26T19:31:40.8714680Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:31:40.8715522Z  ) 2025-08-26T19:31:40.8715976Z  all_branches: bool = ( 2025-08-26T19:31:40.8716735Z  False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:31:40.8717535Z  ) 2025-08-26T19:31:40.8717970Z  default: bool = ( 2025-08-26T19:31:40.8718922Z  True # If True, the experiment is enabled by default for all queries 2025-08-26T19:31:40.8719677Z  ) 2025-08-26T19:31:40.8720144Z  2025-08-26T19:31:40.8720566Z  # Add more fields as needed 2025-08-26T19:31:40.8721161Z  2025-08-26T19:31:40.8721604Z  2025-08-26T19:31:40.8722215Z class Settings(NamedTuple): 2025-08-26T19:31:40.8722921Z  """ 2025-08-26T19:31:40.8723494Z  Settings for the experiments that can be opted into. 2025-08-26T19:31:40.8724165Z  """ 2025-08-26T19:31:40.8724607Z  2025-08-26T19:31:40.8725123Z  experiments: dict[str, Experiment] = {} 2025-08-26T19:31:40.8725700Z  2025-08-26T19:31:40.8726347Z  2025-08-26T19:31:40.8726901Z class ColorFormatter(logging.Formatter): 2025-08-26T19:31:40.8727617Z  """Color codes the log messages based on the log level""" 2025-08-26T19:31:40.8728335Z  2025-08-26T19:31:40.8728761Z  COLORS = { 2025-08-26T19:31:40.8729296Z  "WARNING": "\033[33m", # Yellow 2025-08-26T19:31:40.8729894Z  "ERROR": "\033[31m", # Red 2025-08-26T19:31:40.8730576Z  "CRITICAL": "\033[31m", # Red 2025-08-26T19:31:40.8731194Z  "INFO": "\033[0m", # Reset 2025-08-26T19:31:40.8731745Z  "DEBUG": "\033[0m", # Reset 2025-08-26T19:31:40.8732515Z  } 2025-08-26T19:31:40.8732936Z  2025-08-26T19:31:40.8733528Z  def format(self, record: LogRecord) -> str: 2025-08-26T19:31:40.8734367Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:31:40.8735244Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:31:40.8735956Z  return super().format(record) 2025-08-26T19:31:40.8736526Z  2025-08-26T19:31:40.8736951Z  2025-08-26T19:31:40.8737411Z handler = logging.StreamHandler() 2025-08-26T19:31:40.8738308Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:31:40.8739066Z  2025-08-26T19:31:40.8739594Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:31:40.8740340Z log.addHandler(handler) 2025-08-26T19:31:40.8740878Z log.setLevel(logging.INFO) 2025-08-26T19:31:40.8741445Z  2025-08-26T19:31:40.8741900Z  2025-08-26T19:31:40.8742781Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:31:40.8743427Z  """ 2025-08-26T19:31:40.8744108Z  Defines outputs of the github action that invokes this script 2025-08-26T19:31:40.8745025Z  """ 2025-08-26T19:31:40.8745502Z  if not GITHUB_OUTPUT: 2025-08-26T19:31:40.8746745Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-26T19:31:40.8747887Z  log.warning( 2025-08-26T19:31:40.8748829Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-26T19:31:40.8749880Z  ) 2025-08-26T19:31:40.8750395Z  print(f"::set-output name={key}::{value}") 2025-08-26T19:31:40.8751025Z  return 2025-08-26T19:31:40.8819418Z  2025-08-26T19:31:40.8820143Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:31:40.8821173Z  log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:31:40.8822434Z  f.write(f"{key}={value}\n") 2025-08-26T19:31:40.8823346Z  2025-08-26T19:31:40.8823923Z  2025-08-26T19:31:40.8824834Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:31:40.8825818Z  return frozenset( 2025-08-26T19:31:40.8826478Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:31:40.8827145Z  ) 2025-08-26T19:31:40.8827496Z  2025-08-26T19:31:40.8827833Z  2025-08-26T19:31:40.8828184Z def parse_args() -> Any: 2025-08-26T19:31:40.8828776Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:31:40.8829654Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:31:40.8830416Z  parser.add_argument( 2025-08-26T19:31:40.8830894Z  "--github-issue-repo", 2025-08-26T19:31:40.8831379Z  type=str, 2025-08-26T19:31:40.8831824Z  required=False, 2025-08-26T19:31:40.8833173Z  default="pytorch/test-infra", 2025-08-26T19:31:40.8833765Z  help="GitHub repo to get the issue", 2025-08-26T19:31:40.8834269Z  ) 2025-08-26T19:31:40.8834646Z  parser.add_argument( 2025-08-26T19:31:40.8835113Z  "--github-repo", 2025-08-26T19:31:40.8835566Z  type=str, 2025-08-26T19:31:40.8836003Z  required=True, 2025-08-26T19:31:40.8836510Z  help="GitHub repo where CI is running", 2025-08-26T19:31:40.8837024Z  ) 2025-08-26T19:31:40.8837398Z  parser.add_argument( 2025-08-26T19:31:40.8838036Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:31:40.8838692Z  ) 2025-08-26T19:31:40.8839079Z  parser.add_argument( 2025-08-26T19:31:40.8839738Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:31:40.8840405Z  ) 2025-08-26T19:31:40.8840782Z  parser.add_argument( 2025-08-26T19:31:40.8841441Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:31:40.8842317Z  ) 2025-08-26T19:31:40.8842712Z  parser.add_argument( 2025-08-26T19:31:40.8843379Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:31:40.8844079Z  ) 2025-08-26T19:31:40.8844456Z  parser.add_argument( 2025-08-26T19:31:40.8844920Z  "--github-ref-type", 2025-08-26T19:31:40.8845391Z  type=str, 2025-08-26T19:31:40.8845813Z  required=True, 2025-08-26T19:31:40.8846331Z  help="Current GitHub ref type, branch or tag", 2025-08-26T19:31:40.8846875Z  ) 2025-08-26T19:31:40.8847252Z  parser.add_argument( 2025-08-26T19:31:40.8847931Z  "--eligible-experiments", 2025-08-26T19:31:40.8848472Z  type=_str_comma_separated_to_set, 2025-08-26T19:31:40.8848990Z  required=False, 2025-08-26T19:31:40.8849442Z  default="", 2025-08-26T19:31:40.8850308Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:31:40.8851195Z  ) 2025-08-26T19:31:40.8851569Z  parser.add_argument( 2025-08-26T19:31:40.8852242Z  "--opt-out-experiments", 2025-08-26T19:31:40.8852876Z  type=_str_comma_separated_to_set, 2025-08-26T19:31:40.8853400Z  required=False, 2025-08-26T19:31:40.8853844Z  default="", 2025-08-26T19:31:40.8854271Z  help=( 2025-08-26T19:31:40.8854973Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:31:40.8856087Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:31:40.8856891Z  ), 2025-08-26T19:31:40.8857265Z  ) 2025-08-26T19:31:40.8857642Z  parser.add_argument( 2025-08-26T19:31:40.8858095Z  "--pr-number", 2025-08-26T19:31:40.8858537Z  type=str, 2025-08-26T19:31:40.8858960Z  required=False, 2025-08-26T19:31:40.8859405Z  default="", 2025-08-26T19:31:40.8859917Z  help="the optional PR number where this is run", 2025-08-26T19:31:40.8860455Z  ) 2025-08-26T19:31:40.8860804Z  2025-08-26T19:31:40.8861170Z  return parser.parse_args() 2025-08-26T19:31:40.8861635Z  2025-08-26T19:31:40.8861959Z  2025-08-26T19:31:40.8863214Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:31:40.8864132Z  auth = Auth.Token(github_token) 2025-08-26T19:31:40.8864658Z  return Github(auth=auth) 2025-08-26T19:31:40.8865119Z  2025-08-26T19:31:40.8865446Z  2025-08-26T19:31:40.8866082Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:31:40.8866849Z  repo = gh.get_repo(repo) 2025-08-26T19:31:40.8867377Z  return repo.get_issue(number=issue_num) 2025-08-26T19:31:40.8867876Z  2025-08-26T19:31:40.8868204Z  2025-08-26T19:31:40.8868568Z def get_potential_pr_author( 2025-08-26T19:31:40.8869228Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:31:40.8869887Z ) -> str: 2025-08-26T19:31:40.8870421Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:31:40.8871218Z  # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:31:40.8871978Z  # embedded in the tag name: ciflow// 2025-08-26T19:31:40.8872787Z  2025-08-26T19:31:40.8873166Z  gh = get_gh_client(github_token) 2025-08-26T19:31:40.8873641Z  2025-08-26T19:31:40.8874113Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:31:40.8874734Z  split_tag = ref_name.split("/") 2025-08-26T19:31:40.8875267Z  if ( 2025-08-26T19:31:40.8875668Z  len(split_tag) == 3 2025-08-26T19:31:40.8876182Z  and split_tag[0] == "ciflow" 2025-08-26T19:31:40.8876706Z  and split_tag[2].isnumeric() 2025-08-26T19:31:40.8877189Z  ): 2025-08-26T19:31:40.8877597Z  pr_number = split_tag[2] 2025-08-26T19:31:40.8878076Z  try: 2025-08-26T19:31:40.8878529Z  repository = gh.get_repo(repo) 2025-08-26T19:31:40.8879280Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:31:40.8879886Z  except Exception as e: 2025-08-26T19:31:40.8880425Z  raise Exception( # noqa: TRY002 2025-08-26T19:31:40.8881085Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:31:40.8881716Z  ) from e 2025-08-26T19:31:40.8882592Z  return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:31:40.8883310Z  # In all other cases, return the original input username 2025-08-26T19:31:40.8883891Z  return username 2025-08-26T19:31:40.8884305Z  2025-08-26T19:31:40.8884644Z  2025-08-26T19:31:40.8885063Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:31:40.8885599Z  """ 2025-08-26T19:31:40.8886256Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:31:40.8887018Z  """ 2025-08-26T19:31:40.8887569Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:31:40.8888220Z  2025-08-26T19:31:40.8888547Z  2025-08-26T19:31:40.8888923Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:31:40.8889413Z  try: 2025-08-26T19:31:40.8889814Z  data = yaml.safe_load(yaml_text) 2025-08-26T19:31:40.8890319Z  return data 2025-08-26T19:31:40.8890756Z  except yaml.YAMLError: 2025-08-26T19:31:40.8891265Z  log.exception("Error loading YAML") 2025-08-26T19:31:40.8891760Z  raise 2025-08-26T19:31:40.8892282Z  2025-08-26T19:31:40.8892613Z  2025-08-26T19:31:40.8893210Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:31:40.8893943Z  """ 2025-08-26T19:31:40.8894702Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:31:40.8895439Z  2025-08-26T19:31:40.8895962Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:40.8896703Z  and the text below is the list of opted in users. 2025-08-26T19:31:40.8897234Z  2025-08-26T19:31:40.8897780Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:31:40.8898449Z  """ 2025-08-26T19:31:40.8898903Z  rollout_state_parts = rollout_state.split("---") 2025-08-26T19:31:40.8899492Z  if len(rollout_state_parts) >= 2: 2025-08-26T19:31:40.8900103Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:31:40.8900681Z  else: 2025-08-26T19:31:40.8901076Z  return "", rollout_state 2025-08-26T19:31:40.8901539Z  2025-08-26T19:31:40.8901861Z  2025-08-26T19:31:40.8902368Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:31:40.8902871Z  """ 2025-08-26T19:31:40.8903391Z  Dictionary of users with a list of features they have opted into 2025-08-26T19:31:40.8904018Z  """ 2025-08-26T19:31:40.8904357Z  2025-08-26T19:31:40.8904684Z  2025-08-26T19:31:40.8905219Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:31:40.8905854Z  """ 2025-08-26T19:31:40.8906565Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-26T19:31:40.8907351Z  2025-08-26T19:31:40.8908123Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-26T19:31:40.8909069Z  - Example line: "@User1,lf,split_build" 2025-08-26T19:31:40.8909878Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:31:40.8910483Z  2025-08-26T19:31:40.8910796Z  2025-08-26T19:31:40.8911110Z  """ 2025-08-26T19:31:40.8911478Z  optins = UserOptins() 2025-08-26T19:31:40.8911990Z  for user in user_optin_text.split("\n"): 2025-08-26T19:31:40.8912648Z  user = user.strip("\r\n\t -") 2025-08-26T19:31:40.8913199Z  if not user or not user.startswith("@"): 2025-08-26T19:31:40.8913742Z  # Not a valid user. Skip 2025-08-26T19:31:40.8914219Z  continue 2025-08-26T19:31:40.8914626Z  2025-08-26T19:31:40.8914955Z  if user: 2025-08-26T19:31:40.8915416Z  usr_name = user.split(",")[0].strip("@") 2025-08-26T19:31:40.8916089Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:31:40.8916714Z  2025-08-26T19:31:40.8917053Z  return optins 2025-08-26T19:31:40.8917453Z  2025-08-26T19:31:40.8917773Z  2025-08-26T19:31:40.8918250Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:31:40.8918835Z  """ 2025-08-26T19:31:40.8919242Z  Check if the experiment name is valid. 2025-08-26T19:31:40.8919753Z  A valid name: 2025-08-26T19:31:40.8920410Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:31:40.8921321Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:31:40.8922243Z  - Cannot contain spaces 2025-08-26T19:31:40.8922811Z  """ 2025-08-26T19:31:40.8923162Z  2025-08-26T19:31:40.8923613Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:31:40.8924336Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:31:40.8925039Z  2025-08-26T19:31:40.8925387Z  if valid: 2025-08-26T19:31:40.8925783Z  return True 2025-08-26T19:31:40.8926190Z  2025-08-26T19:31:40.8926522Z  log.error( 2025-08-26T19:31:40.8927903Z  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-08-26T19:31:40.8929348Z  ) 2025-08-26T19:31:40.8929726Z  return False 2025-08-26T19:31:40.8930116Z  2025-08-26T19:31:40.8930439Z  2025-08-26T19:31:40.8930934Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:31:40.8931556Z  """ 2025-08-26T19:31:40.8932369Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:31:40.8933097Z  """ 2025-08-26T19:31:40.8933458Z  try: 2025-08-26T19:31:40.8933834Z  if settings_text: 2025-08-26T19:31:40.8934562Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-26T19:31:40.8935315Z  # for easy reading 2025-08-26T19:31:40.8936116Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:31:40.8936982Z  # the backtick character in shell commands. 2025-08-26T19:31:40.8937580Z  backtick = chr(96) # backtick character 2025-08-26T19:31:40.8938242Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:31:40.8938940Z  settings = load_yaml(settings_text) 2025-08-26T19:31:40.8939437Z  2025-08-26T19:31:40.8940150Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:31:40.8940867Z  experiments = {} 2025-08-26T19:31:40.8941310Z  2025-08-26T19:31:40.8941858Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:31:40.8942727Z  if not is_valid_experiment_name(exp_name): 2025-08-26T19:31:40.8943794Z  # 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-08-26T19:31:40.8944778Z  continue 2025-08-26T19:31:40.8945209Z  2025-08-26T19:31:40.8945573Z  valid_settings = {} 2025-08-26T19:31:40.8946098Z  for setting in exp_settings: 2025-08-26T19:31:40.8946664Z  if setting not in Experiment._fields: 2025-08-26T19:31:40.8947218Z  log.warning( 2025-08-26T19:31:40.8947913Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:31:40.8948602Z  ) 2025-08-26T19:31:40.8949042Z  else: 2025-08-26T19:31:40.8949572Z  valid_settings[setting] = exp_settings[setting] 2025-08-26T19:31:40.8950121Z  2025-08-26T19:31:40.8950587Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:31:40.8951213Z  return Settings(experiments) 2025-08-26T19:31:40.8951694Z  2025-08-26T19:31:40.8952147Z  except Exception: 2025-08-26T19:31:40.8952653Z  log.exception("Failed to parse settings") 2025-08-26T19:31:40.8953179Z  2025-08-26T19:31:40.8953524Z  return Settings() 2025-08-26T19:31:40.8953933Z  2025-08-26T19:31:40.8954255Z  2025-08-26T19:31:40.8954806Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:31:40.8955365Z  """ 2025-08-26T19:31:40.8955861Z  Parse settings, if any, from the rollout state. 2025-08-26T19:31:40.8956398Z  2025-08-26T19:31:40.8956913Z  If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:40.8957667Z  and the text below is the list of opted in users. 2025-08-26T19:31:40.8958201Z  2025-08-26T19:31:40.8958775Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:31:40.8959469Z  """ 2025-08-26T19:31:40.8960022Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:40.8960767Z  return parse_settings_from_text(settings_text) 2025-08-26T19:31:40.8961289Z  2025-08-26T19:31:40.8961615Z  2025-08-26T19:31:40.8962275Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:31:40.8962909Z  """ 2025-08-26T19:31:40.8963330Z  Parse users from the rollout state. 2025-08-26T19:31:40.8963811Z  2025-08-26T19:31:40.8964135Z  """ 2025-08-26T19:31:40.8964669Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:40.8965397Z  return parse_user_opt_in_from_text(users_text) 2025-08-26T19:31:40.8965911Z  2025-08-26T19:31:40.8966236Z  2025-08-26T19:31:40.8966838Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:40.8967553Z  """ 2025-08-26T19:31:40.8967985Z  Check if a user is opted into an experiment 2025-08-26T19:31:40.8968503Z  """ 2025-08-26T19:31:40.8968971Z  return experiment_name in user_optins.get(user, []) 2025-08-26T19:31:40.8969676Z  2025-08-26T19:31:40.8970003Z  2025-08-26T19:31:40.8970606Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:40.8971323Z  """ 2025-08-26T19:31:40.8971798Z  Check if a user explicitly opted out of an experiment 2025-08-26T19:31:40.8972583Z  """ 2025-08-26T19:31:40.8973103Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:31:40.8973784Z  experiment_optout = "-" + experiment_name 2025-08-26T19:31:40.8974425Z  if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:31:40.8975031Z  return False 2025-08-26T19:31:40.8975437Z  2025-08-26T19:31:40.8975901Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:31:40.8976475Z  log.warning( 2025-08-26T19:31:40.8977282Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:31:40.8978106Z  ) 2025-08-26T19:31:40.8978466Z  2025-08-26T19:31:40.8978813Z  return True 2025-08-26T19:31:40.8979200Z  2025-08-26T19:31:40.8979521Z  2025-08-26T19:31:40.8979866Z def get_runner_prefix( 2025-08-26T19:31:40.8980319Z  rollout_state: str, 2025-08-26T19:31:40.8980805Z  workflow_requestors: Iterable[str], 2025-08-26T19:31:40.8981317Z  branch: str, 2025-08-26T19:31:40.8981833Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:40.8982617Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:40.8983194Z  is_canary: bool = False, 2025-08-26T19:31:40.8983645Z ) -> str: 2025-08-26T19:31:40.8984081Z  settings = parse_settings(rollout_state) 2025-08-26T19:31:40.8984652Z  user_optins = parse_users(rollout_state) 2025-08-26T19:31:40.8985157Z  2025-08-26T19:31:40.8985637Z  fleet_prefix = "" 2025-08-26T19:31:40.8986090Z  prefixes = [] 2025-08-26T19:31:40.8986765Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:31:40.8987683Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:31:40.8988373Z  log.info( 2025-08-26T19:31:40.8989062Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:31:40.8989791Z  ) 2025-08-26T19:31:40.8990180Z  continue 2025-08-26T19:31:40.8990585Z  2025-08-26T19:31:40.8990950Z  if opt_out_experiments: 2025-08-26T19:31:40.8991493Z  if experiment_name in opt_out_experiments: 2025-08-26T19:31:40.8992218Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:31:40.8992798Z  log.info( 2025-08-26T19:31:40.8993697Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:31:40.8994613Z  ) 2025-08-26T19:31:40.8995023Z  continue 2025-08-26T19:31:40.8995448Z  2025-08-26T19:31:40.8995800Z  if eligible_experiments: 2025-08-26T19:31:40.8996373Z  if experiment_name not in eligible_experiments: 2025-08-26T19:31:40.8996987Z  exp_list = ", ".join(eligible_experiments) 2025-08-26T19:31:40.8997521Z  log.info( 2025-08-26T19:31:40.8998285Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:31:40.8999077Z  ) 2025-08-26T19:31:40.8999610Z  continue 2025-08-26T19:31:40.9000108Z  elif not experiment_settings.default: 2025-08-26T19:31:40.9000628Z  log.info( 2025-08-26T19:31:40.9001283Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:31:40.9001983Z  ) 2025-08-26T19:31:40.9002638Z  continue 2025-08-26T19:31:40.9003071Z  2025-08-26T19:31:40.9003527Z  # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:31:40.9004123Z  opted_out_users = [ 2025-08-26T19:31:40.9004587Z  requestor 2025-08-26T19:31:40.9005062Z  for requestor in workflow_requestors 2025-08-26T19:31:40.9005730Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:31:40.9006326Z  ] 2025-08-26T19:31:40.9006693Z  2025-08-26T19:31:40.9007044Z  if opted_out_users: 2025-08-26T19:31:40.9007502Z  log.info( 2025-08-26T19:31:40.9008144Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:31:40.9008810Z  ) 2025-08-26T19:31:40.9009202Z  continue 2025-08-26T19:31:40.9009598Z  2025-08-26T19:31:40.9010048Z  # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:31:40.9010649Z  opted_in_users = [ 2025-08-26T19:31:40.9011111Z  requestor 2025-08-26T19:31:40.9011588Z  for requestor in workflow_requestors 2025-08-26T19:31:40.9012466Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:31:40.9013072Z  ] 2025-08-26T19:31:40.9013428Z  2025-08-26T19:31:40.9013781Z  enabled = False 2025-08-26T19:31:40.9014242Z  if opted_in_users: 2025-08-26T19:31:40.9014849Z  log.info( 2025-08-26T19:31:40.9015473Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:31:40.9016121Z  ) 2025-08-26T19:31:40.9016518Z  enabled = True 2025-08-26T19:31:40.9016949Z  2025-08-26T19:31:40.9017352Z  elif experiment_settings.rollout_perc: 2025-08-26T19:31:40.9018144Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:31:40.9019039Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:31:40.9019660Z  log.info( 2025-08-26T19:31:40.9020504Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:31:40.9021374Z  ) 2025-08-26T19:31:40.9021795Z  enabled = True 2025-08-26T19:31:40.9022389Z  2025-08-26T19:31:40.9022734Z  if enabled: 2025-08-26T19:31:40.9023172Z  label = experiment_name 2025-08-26T19:31:40.9023741Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:31:40.9024531Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:31:40.9025373Z  # - If it's enabled, then we always list it's prefix first 2025-08-26T19:31:40.9026103Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:31:40.9026745Z  if is_canary: 2025-08-26T19:31:40.9027255Z  label += CANARY_FLEET_SUFFIX 2025-08-26T19:31:40.9027786Z  fleet_prefix = label 2025-08-26T19:31:40.9028395Z  else: 2025-08-26T19:31:40.9028842Z  prefixes.append(label) 2025-08-26T19:31:40.9029322Z  2025-08-26T19:31:40.9029665Z  if len(prefixes) > 1: 2025-08-26T19:31:40.9030108Z  log.error( 2025-08-26T19:31:40.9031121Z  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-08-26T19:31:40.9032266Z  ) 2025-08-26T19:31:40.9032669Z  prefixes = prefixes[:1] 2025-08-26T19:31:40.9033126Z  2025-08-26T19:31:40.9033482Z  # Fleet always comes first 2025-08-26T19:31:40.9033949Z  if fleet_prefix: 2025-08-26T19:31:40.9034417Z  prefixes.insert(0, fleet_prefix) 2025-08-26T19:31:40.9034908Z  2025-08-26T19:31:40.9035336Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:31:40.9035888Z  2025-08-26T19:31:40.9036203Z  2025-08-26T19:31:40.9036820Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:31:40.9037548Z  """ 2025-08-26T19:31:40.9038132Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:31:40.9038812Z  2025-08-26T19:31:40.9039359Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:31:40.9040029Z  """ 2025-08-26T19:31:40.9040422Z  gh = get_gh_client(github_token) 2025-08-26T19:31:40.9040971Z  issue = get_issue(gh, repo, issue_num) 2025-08-26T19:31:40.9041595Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:31:40.9042439Z  2025-08-26T19:31:40.9042775Z  2025-08-26T19:31:40.9043363Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:31:40.9044232Z  for _ in range(num_retries): 2025-08-26T19:31:40.9044709Z  try: 2025-08-26T19:31:40.9045150Z  req = Request(url=url, headers=headers) 2025-08-26T19:31:40.9045790Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:31:40.9046412Z  return json.loads(content) 2025-08-26T19:31:40.9046924Z  except Exception as e: 2025-08-26T19:31:40.9047474Z  log.warning(f"Could not download {url}: {e}") 2025-08-26T19:31:40.9048004Z  2025-08-26T19:31:40.9048557Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:31:40.9049240Z  return {} 2025-08-26T19:31:40.9049616Z  2025-08-26T19:31:40.9049935Z  2025-08-26T19:31:40.9050343Z @cache 2025-08-26T19:31:40.9050968Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:31:40.9051692Z  """ 2025-08-26T19:31:40.9052315Z  Dynamically get PR information 2025-08-26T19:31:40.9052812Z  """ 2025-08-26T19:31:40.9053308Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:31:40.9053904Z  headers = { 2025-08-26T19:31:40.9054374Z  "Accept": "application/vnd.github.v3+json", 2025-08-26T19:31:40.9054973Z  "Authorization": f"token {github_token}", 2025-08-26T19:31:40.9055481Z  } 2025-08-26T19:31:40.9055912Z  json_response: dict[str, Any] = download_json( 2025-08-26T19:31:40.9056513Z  url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:31:40.9057036Z  headers=headers, 2025-08-26T19:31:40.9057470Z  ) 2025-08-26T19:31:40.9057807Z  2025-08-26T19:31:40.9058152Z  if not json_response: 2025-08-26T19:31:40.9058879Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:31:40.9059488Z  return {} 2025-08-26T19:31:40.9059888Z  2025-08-26T19:31:40.9060233Z  return json_response 2025-08-26T19:31:40.9060662Z  2025-08-26T19:31:40.9060986Z  2025-08-26T19:31:40.9061555Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:31:40.9062363Z  """ 2025-08-26T19:31:40.9062899Z  Dynamically get the latest list of labels from the pull request 2025-08-26T19:31:40.9063523Z  """ 2025-08-26T19:31:40.9064006Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:31:40.9064586Z  return { 2025-08-26T19:31:40.9065171Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:31:40.9065822Z  } 2025-08-26T19:31:40.9066158Z  2025-08-26T19:31:40.9066492Z  2025-08-26T19:31:40.9066836Z def main() -> None: 2025-08-26T19:31:40.9067269Z  args = parse_args() 2025-08-26T19:31:40.9067702Z  2025-08-26T19:31:40.9068106Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:31:40.9068621Z  2025-08-26T19:31:40.9068976Z  # Check if the PR is opt-out 2025-08-26T19:31:40.9069467Z  if args.pr_number: 2025-08-26T19:31:40.9070121Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:31:40.9070841Z  if OPT_OUT_LABEL in labels: 2025-08-26T19:31:40.9071322Z  log.info( 2025-08-26T19:31:40.9072009Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:31:40.9072876Z  ) 2025-08-26T19:31:40.9073437Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:40.9074092Z  sys.exit() 2025-08-26T19:31:40.9074623Z  2025-08-26T19:31:40.9074961Z  try: 2025-08-26T19:31:40.9075434Z  rollout_state = get_rollout_state_from_issue( 2025-08-26T19:31:40.9076130Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:31:40.9076743Z  ) 2025-08-26T19:31:40.9077097Z  2025-08-26T19:31:40.9077486Z  username = get_potential_pr_author( 2025-08-26T19:31:40.9078001Z  args.github_token, 2025-08-26T19:31:40.9078482Z  args.github_repo, 2025-08-26T19:31:40.9078955Z  args.github_actor, 2025-08-26T19:31:40.9079450Z  args.github_ref_type, 2025-08-26T19:31:40.9079942Z  args.github_branch, 2025-08-26T19:31:40.9080392Z  ) 2025-08-26T19:31:40.9080746Z  2025-08-26T19:31:40.9081210Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:31:40.9081783Z  2025-08-26T19:31:40.9082456Z  runner_label_prefix = get_runner_prefix( 2025-08-26T19:31:40.9083004Z  rollout_state, 2025-08-26T19:31:40.9083509Z  (args.github_issue_owner, username), 2025-08-26T19:31:40.9084052Z  args.github_branch, 2025-08-26T19:31:40.9084560Z  args.eligible_experiments, 2025-08-26T19:31:40.9085083Z  args.opt_out_experiments, 2025-08-26T19:31:40.9085577Z  is_canary, 2025-08-26T19:31:40.9085994Z  ) 2025-08-26T19:31:40.9086352Z  2025-08-26T19:31:40.9086710Z  except Exception as e: 2025-08-26T19:31:40.9087175Z  log.error( 2025-08-26T19:31:40.9087844Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:31:40.9088695Z  ) 2025-08-26T19:31:40.9089069Z  2025-08-26T19:31:40.9089571Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:40.9090198Z  2025-08-26T19:31:40.9090515Z  2025-08-26T19:31:40.9090861Z if __name__ == "__main__": 2025-08-26T19:31:40.9091300Z  main() 2025-08-26T19:31:40.9091666Z  2025-08-26T19:31:40.9091984Z EOF 2025-08-26T19:31:40.9092555Z  2025-08-26T19:31:40.9092926Z cat runner_determinator.py 2025-08-26T19:31:41.0661425Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:41.0662556Z env: 2025-08-26T19:31:41.0663308Z GITHUB_TOKEN: *** 2025-08-26T19:31:41.0663765Z ISSUE_NUMBER: 5132 2025-08-26T19:31:41.0664261Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:41.0664817Z ISSUE_OWNER: 2025-08-26T19:31:41.0665250Z CHECK_EXPERIMENTS: 2025-08-26T19:31:41.0665716Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:31:41.0666212Z PR_NUMBER: 2025-08-26T19:31:41.0666612Z ##[endgroup] 2025-08-26T19:31:41.0884431Z # flake8: noqa: G004 2025-08-26T19:31:41.0884756Z 2025-08-26T19:31:41.0885165Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-26T19:31:41.0886037Z # must be kept in sync. You can do it easily by running the following command: 2025-08-26T19:31:41.0886797Z # python .github/scripts/update_runner_determinator.py 2025-08-26T19:31:41.0887217Z 2025-08-26T19:31:41.0887362Z """ 2025-08-26T19:31:41.0887902Z This runner determinator is used to determine which set of runners to run a 2025-08-26T19:31:41.0888708Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-26T19:31:41.0889532Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-26T19:31:41.0890278Z of which runners should be used to run which job. 2025-08-26T19:31:41.0890648Z 2025-08-26T19:31:41.0891011Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-26T19:31:41.0892259Z separated by a line containing "---". If the line is not present, the 2025-08-26T19:31:41.0893200Z settings are considered to be empty with only the second part, the user 2025-08-26T19:31:41.0893839Z list, defined. 2025-08-26T19:31:41.0894051Z 2025-08-26T19:31:41.0894385Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-26T19:31:41.0895220Z used to define any settings that are needed to determine which runners to use. 2025-08-26T19:31:41.0895983Z It's fields are defined by the RolloutSettings class below. 2025-08-26T19:31:41.0896392Z 2025-08-26T19:31:41.0896738Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-26T19:31:41.0897526Z The user list is also a comma separated list of additional features or 2025-08-26T19:31:41.0898201Z experiments which the user could be opted in to. 2025-08-26T19:31:41.0898577Z 2025-08-26T19:31:41.0898758Z The user list has the following rules: 2025-08-26T19:31:41.0899093Z 2025-08-26T19:31:41.0899389Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-26T19:31:41.0900193Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-26T19:31:41.0900896Z - A "#" prefix opts the user out of all experiments 2025-08-26T19:31:41.0901257Z 2025-08-26T19:31:41.0901416Z Example config: 2025-08-26T19:31:41.0901826Z # A list of experiments that can be opted into. 2025-08-26T19:31:41.0902666Z # This defines the behavior they'll induce when opted into. 2025-08-26T19:31:41.0903238Z # Expected syntax is: 2025-08-26T19:31:41.0903827Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-26T19:31:41.0904707Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-26T19:31:41.0905272Z 2025-08-26T19:31:41.0905422Z experiments: 2025-08-26T19:31:41.0905783Z lf: 2025-08-26T19:31:41.0906120Z rollout_percent: 25 2025-08-26T19:31:41.0906721Z all_branches: false 2025-08-26T19:31:41.0907132Z default: true 2025-08-26T19:31:41.0907506Z --- 2025-08-26T19:31:41.0907692Z 2025-08-26T19:31:41.0907843Z # Opt-ins: 2025-08-26T19:31:41.0908372Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-26T19:31:41.0909155Z # and specifying experiments to enable in a comma-separated list. 2025-08-26T19:31:41.0909873Z # To always opt out of an experiment, prefix it with a "-". 2025-08-26T19:31:41.0910465Z # Experiments should be from the above list. 2025-08-26T19:31:41.0910814Z 2025-08-26T19:31:41.0910977Z @User1,-lf,split_build 2025-08-26T19:31:41.0911379Z @User2,lf 2025-08-26T19:31:41.0911722Z @User3,split_build 2025-08-26T19:31:41.0912361Z """ 2025-08-26T19:31:41.0912549Z 2025-08-26T19:31:41.0912700Z import json 2025-08-26T19:31:41.0913040Z import logging 2025-08-26T19:31:41.0913377Z import os 2025-08-26T19:31:41.0913711Z import random 2025-08-26T19:31:41.0914057Z import re 2025-08-26T19:31:41.0914379Z import sys 2025-08-26T19:31:41.0914750Z from argparse import ArgumentParser 2025-08-26T19:31:41.0915230Z from collections.abc import Iterable 2025-08-26T19:31:41.0915702Z from functools import cache 2025-08-26T19:31:41.0916125Z from logging import LogRecord 2025-08-26T19:31:41.0916570Z from typing import Any, NamedTuple 2025-08-26T19:31:41.0917050Z from urllib.request import Request, urlopen 2025-08-26T19:31:41.0917392Z 2025-08-26T19:31:41.0917539Z import yaml 2025-08-26T19:31:41.0917890Z from github import Auth, Github 2025-08-26T19:31:41.0918336Z from github.Issue import Issue 2025-08-26T19:31:41.0918612Z 2025-08-26T19:31:41.0918618Z 2025-08-26T19:31:41.0918821Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-26T19:31:41.0919434Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-26T19:31:41.0920223Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-26T19:31:41.0920728Z 2025-08-26T19:31:41.0920939Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-26T19:31:41.0921583Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-26T19:31:41.0922240Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-26T19:31:41.0922825Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-26T19:31:41.0923149Z 2025-08-26T19:31:41.0923330Z SETTING_EXPERIMENTS = "experiments" 2025-08-26T19:31:41.0923630Z 2025-08-26T19:31:41.0923798Z LF_FLEET_EXPERIMENT = "lf" 2025-08-26T19:31:41.0924219Z CANARY_FLEET_SUFFIX = ".c" 2025-08-26T19:31:41.0924476Z 2025-08-26T19:31:41.0924482Z 2025-08-26T19:31:41.0924653Z class Experiment(NamedTuple): 2025-08-26T19:31:41.0925086Z rollout_perc: float = ( 2025-08-26T19:31:41.0925655Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-26T19:31:41.0926270Z ) 2025-08-26T19:31:41.0926610Z all_branches: bool = ( 2025-08-26T19:31:41.0927168Z False # If True, the experiment is also enabled on the exception branches 2025-08-26T19:31:41.0927830Z ) 2025-08-26T19:31:41.0928147Z default: bool = ( 2025-08-26T19:31:41.0928678Z True # If True, the experiment is enabled by default for all queries 2025-08-26T19:31:41.0929257Z ) 2025-08-26T19:31:41.0929433Z 2025-08-26T19:31:41.0929596Z # Add more fields as needed 2025-08-26T19:31:41.0929867Z 2025-08-26T19:31:41.0929872Z 2025-08-26T19:31:41.0930045Z class Settings(NamedTuple): 2025-08-26T19:31:41.0930442Z """ 2025-08-26T19:31:41.0930849Z Settings for the experiments that can be opted into. 2025-08-26T19:31:41.0931362Z """ 2025-08-26T19:31:41.0931540Z 2025-08-26T19:31:41.0931731Z experiments: dict[str, Experiment] = {} 2025-08-26T19:31:41.0932256Z 2025-08-26T19:31:41.0932266Z 2025-08-26T19:31:41.0932495Z class ColorFormatter(logging.Formatter): 2025-08-26T19:31:41.0933083Z """Color codes the log messages based on the log level""" 2025-08-26T19:31:41.0933480Z 2025-08-26T19:31:41.0933631Z COLORS = { 2025-08-26T19:31:41.0933988Z "WARNING": "\033[33m", # Yellow 2025-08-26T19:31:41.0934600Z "ERROR": "\033[31m", # Red 2025-08-26T19:31:41.0935050Z "CRITICAL": "\033[31m", # Red 2025-08-26T19:31:41.0935510Z "INFO": "\033[0m", # Reset 2025-08-26T19:31:41.0935947Z "DEBUG": "\033[0m", # Reset 2025-08-26T19:31:41.0936374Z } 2025-08-26T19:31:41.0936550Z 2025-08-26T19:31:41.0936755Z def format(self, record: LogRecord) -> str: 2025-08-26T19:31:41.0937442Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-26T19:31:41.0938140Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-26T19:31:41.0938665Z return super().format(record) 2025-08-26T19:31:41.0938969Z 2025-08-26T19:31:41.0938976Z 2025-08-26T19:31:41.0939159Z handler = logging.StreamHandler() 2025-08-26T19:31:41.0939795Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-26T19:31:41.0940306Z 2025-08-26T19:31:41.0940524Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-26T19:31:41.0941054Z log.addHandler(handler) 2025-08-26T19:31:41.0941459Z log.setLevel(logging.INFO) 2025-08-26T19:31:41.0941716Z 2025-08-26T19:31:41.0941723Z 2025-08-26T19:31:41.0941950Z def set_github_output(key: str, value: str) -> None: 2025-08-26T19:31:41.0942690Z """ 2025-08-26T19:31:41.0943147Z Defines outputs of the github action that invokes this script 2025-08-26T19:31:41.0943712Z """ 2025-08-26T19:31:41.0944039Z if not GITHUB_OUTPUT: 2025-08-26T19:31:41.0945005Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-26T19:31:41.0946031Z log.warning( 2025-08-26T19:31:41.0946811Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-26T19:31:41.0947640Z ) 2025-08-26T19:31:41.0957690Z print(f"::set-output name={key}::{value}") 2025-08-26T19:31:41.0958256Z return 2025-08-26T19:31:41.0958499Z 2025-08-26T19:31:41.0958879Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-26T19:31:41.0959420Z log.info(f"Setting output: {key}='{value}'") 2025-08-26T19:31:41.0959954Z f.write(f"{key}={value}\n") 2025-08-26T19:31:41.0960256Z 2025-08-26T19:31:41.0960263Z 2025-08-26T19:31:41.0960546Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-26T19:31:41.0961119Z return frozenset( 2025-08-26T19:31:41.0961678Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-26T19:31:41.0962633Z ) 2025-08-26T19:31:41.0962828Z 2025-08-26T19:31:41.0962835Z 2025-08-26T19:31:41.0963000Z def parse_args() -> Any: 2025-08-26T19:31:41.0963507Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-26T19:31:41.0964306Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-26T19:31:41.0965335Z parser.add_argument( 2025-08-26T19:31:41.0965754Z "--github-issue-repo", 2025-08-26T19:31:41.0966188Z type=str, 2025-08-26T19:31:41.0966551Z required=False, 2025-08-26T19:31:41.0966958Z default="pytorch/test-infra", 2025-08-26T19:31:41.0967433Z help="GitHub repo to get the issue", 2025-08-26T19:31:41.0967900Z ) 2025-08-26T19:31:41.0968225Z parser.add_argument( 2025-08-26T19:31:41.0968628Z "--github-repo", 2025-08-26T19:31:41.0969007Z type=str, 2025-08-26T19:31:41.0969368Z required=True, 2025-08-26T19:31:41.0969779Z help="GitHub repo where CI is running", 2025-08-26T19:31:41.0970258Z ) 2025-08-26T19:31:41.0970590Z parser.add_argument( 2025-08-26T19:31:41.0971131Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-26T19:31:41.0971736Z ) 2025-08-26T19:31:41.0972206Z parser.add_argument( 2025-08-26T19:31:41.0972785Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-26T19:31:41.0973393Z ) 2025-08-26T19:31:41.0973886Z parser.add_argument( 2025-08-26T19:31:41.0974475Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-26T19:31:41.0975108Z ) 2025-08-26T19:31:41.0975441Z parser.add_argument( 2025-08-26T19:31:41.0976022Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-26T19:31:41.0976673Z ) 2025-08-26T19:31:41.0976995Z parser.add_argument( 2025-08-26T19:31:41.0977396Z "--github-ref-type", 2025-08-26T19:31:41.0977797Z type=str, 2025-08-26T19:31:41.0978154Z required=True, 2025-08-26T19:31:41.0978585Z help="Current GitHub ref type, branch or tag", 2025-08-26T19:31:41.0979082Z ) 2025-08-26T19:31:41.0979400Z parser.add_argument( 2025-08-26T19:31:41.0979811Z "--eligible-experiments", 2025-08-26T19:31:41.0980266Z type=_str_comma_separated_to_set, 2025-08-26T19:31:41.0980794Z required=False, 2025-08-26T19:31:41.0981180Z default="", 2025-08-26T19:31:41.0981960Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-26T19:31:41.0982917Z ) 2025-08-26T19:31:41.0983243Z parser.add_argument( 2025-08-26T19:31:41.0983650Z "--opt-out-experiments", 2025-08-26T19:31:41.0984101Z type=_str_comma_separated_to_set, 2025-08-26T19:31:41.0984571Z required=False, 2025-08-26T19:31:41.0984942Z default="", 2025-08-26T19:31:41.0985292Z help=( 2025-08-26T19:31:41.0985902Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-26T19:31:41.0986944Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-26T19:31:41.0987701Z ), 2025-08-26T19:31:41.0988015Z ) 2025-08-26T19:31:41.0988345Z parser.add_argument( 2025-08-26T19:31:41.0988728Z "--pr-number", 2025-08-26T19:31:41.0989110Z type=str, 2025-08-26T19:31:41.0989469Z required=False, 2025-08-26T19:31:41.0989848Z default="", 2025-08-26T19:31:41.0990376Z help="the optional PR number where this is run", 2025-08-26T19:31:41.0990892Z ) 2025-08-26T19:31:41.0991069Z 2025-08-26T19:31:41.0991247Z return parser.parse_args() 2025-08-26T19:31:41.0991524Z 2025-08-26T19:31:41.0991531Z 2025-08-26T19:31:41.0991896Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-26T19:31:41.0992687Z auth = Auth.Token(github_token) 2025-08-26T19:31:41.0993145Z return Github(auth=auth) 2025-08-26T19:31:41.0993418Z 2025-08-26T19:31:41.0993424Z 2025-08-26T19:31:41.0993836Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-26T19:31:41.0994568Z repo = gh.get_repo(repo) 2025-08-26T19:31:41.0995011Z return repo.get_issue(number=issue_num) 2025-08-26T19:31:41.1043403Z 2025-08-26T19:31:41.1043414Z 2025-08-26T19:31:41.1043671Z def get_potential_pr_author( 2025-08-26T19:31:41.1044438Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-26T19:31:41.1045118Z ) -> str: 2025-08-26T19:31:41.1045599Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-26T19:31:41.1046356Z # Fetch the actual username from the original PR. The PR number is 2025-08-26T19:31:41.1047050Z # embedded in the tag name: ciflow// 2025-08-26T19:31:41.1047442Z 2025-08-26T19:31:41.1047618Z gh = get_gh_client(github_token) 2025-08-26T19:31:41.1047928Z 2025-08-26T19:31:41.1048180Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-26T19:31:41.1048745Z split_tag = ref_name.split("/") 2025-08-26T19:31:41.1049208Z if ( 2025-08-26T19:31:41.1049559Z len(split_tag) == 3 2025-08-26T19:31:41.1049996Z and split_tag[0] == "ciflow" 2025-08-26T19:31:41.1050471Z and split_tag[2].isnumeric() 2025-08-26T19:31:41.1050961Z ): 2025-08-26T19:31:41.1051498Z pr_number = split_tag[2] 2025-08-26T19:31:41.1051969Z try: 2025-08-26T19:31:41.1052500Z repository = gh.get_repo(repo) 2025-08-26T19:31:41.1053058Z pull = repository.get_pull(number=int(pr_number)) 2025-08-26T19:31:41.1053605Z except Exception as e: 2025-08-26T19:31:41.1054098Z raise Exception( # noqa: TRY002 2025-08-26T19:31:41.1054710Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-26T19:31:41.1055285Z ) from e 2025-08-26T19:31:41.1055778Z return pull.user.login # type: ignore[no-any-return] 2025-08-26T19:31:41.1056414Z # In all other cases, return the original input username 2025-08-26T19:31:41.1056957Z return username 2025-08-26T19:31:41.1057176Z 2025-08-26T19:31:41.1057183Z 2025-08-26T19:31:41.1057393Z def is_exception_branch(branch: str) -> bool: 2025-08-26T19:31:41.1057884Z """ 2025-08-26T19:31:41.1058499Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-26T19:31:41.1059230Z """ 2025-08-26T19:31:41.1059739Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-26T19:31:41.1060215Z 2025-08-26T19:31:41.1060222Z 2025-08-26T19:31:41.1060406Z def load_yaml(yaml_text: str) -> Any: 2025-08-26T19:31:41.1060851Z try: 2025-08-26T19:31:41.1061205Z data = yaml.safe_load(yaml_text) 2025-08-26T19:31:41.1061663Z return data 2025-08-26T19:31:41.1062183Z except yaml.YAMLError: 2025-08-26T19:31:41.1062628Z log.exception("Error loading YAML") 2025-08-26T19:31:41.1063102Z raise 2025-08-26T19:31:41.1063299Z 2025-08-26T19:31:41.1063305Z 2025-08-26T19:31:41.1063682Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-26T19:31:41.1064367Z """ 2025-08-26T19:31:41.1064956Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-26T19:31:41.1065517Z 2025-08-26T19:31:41.1065959Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:41.1066661Z and the text below is the list of opted in users. 2025-08-26T19:31:41.1067030Z 2025-08-26T19:31:41.1067374Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-26T19:31:41.1068015Z """ 2025-08-26T19:31:41.1068416Z rollout_state_parts = rollout_state.split("---") 2025-08-26T19:31:41.1068967Z if len(rollout_state_parts) >= 2: 2025-08-26T19:31:41.1069533Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-26T19:31:41.1070064Z else: 2025-08-26T19:31:41.1070419Z return "", rollout_state 2025-08-26T19:31:41.1070700Z 2025-08-26T19:31:41.1070706Z 2025-08-26T19:31:41.1070886Z class UserOptins(dict[str, list[str]]): 2025-08-26T19:31:41.1071349Z """ 2025-08-26T19:31:41.1071820Z Dictionary of users with a list of features they have opted into 2025-08-26T19:31:41.1072521Z """ 2025-08-26T19:31:41.1072710Z 2025-08-26T19:31:41.1072716Z 2025-08-26T19:31:41.1073046Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-26T19:31:41.1073630Z """ 2025-08-26T19:31:41.1074280Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-26T19:31:41.1074908Z 2025-08-26T19:31:41.1075479Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-26T19:31:41.1076393Z - Example line: "@User1,lf,split_build" 2025-08-26T19:31:41.1077018Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-26T19:31:41.1077471Z 2025-08-26T19:31:41.1077477Z 2025-08-26T19:31:41.1077619Z """ 2025-08-26T19:31:41.1077958Z optins = UserOptins() 2025-08-26T19:31:41.1078398Z for user in user_optin_text.split("\n"): 2025-08-26T19:31:41.1078905Z user = user.strip("\r\n\t -") 2025-08-26T19:31:41.1079518Z if not user or not user.startswith("@"): 2025-08-26T19:31:41.1080030Z # Not a valid user. Skip 2025-08-26T19:31:41.1080465Z continue 2025-08-26T19:31:41.1080689Z 2025-08-26T19:31:41.1080832Z if user: 2025-08-26T19:31:41.1081226Z usr_name = user.split(",")[0].strip("@") 2025-08-26T19:31:41.1081847Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-26T19:31:41.1082572Z 2025-08-26T19:31:41.1082736Z return optins 2025-08-26T19:31:41.1082957Z 2025-08-26T19:31:41.1082963Z 2025-08-26T19:31:41.1083224Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-26T19:31:41.1083766Z """ 2025-08-26T19:31:41.1084117Z Check if the experiment name is valid. 2025-08-26T19:31:41.1084590Z A valid name: 2025-08-26T19:31:41.1085159Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-26T19:31:41.1086019Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-26T19:31:41.1086687Z - Cannot contain spaces 2025-08-26T19:31:41.1087111Z """ 2025-08-26T19:31:41.1087289Z 2025-08-26T19:31:41.1087532Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-26T19:31:41.1088165Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-26T19:31:41.1088575Z 2025-08-26T19:31:41.1088718Z if valid: 2025-08-26T19:31:41.1089056Z return True 2025-08-26T19:31:41.1089273Z 2025-08-26T19:31:41.1089417Z log.error( 2025-08-26T19:31:41.1090751Z 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-08-26T19:31:41.1092338Z ) 2025-08-26T19:31:41.1092660Z return False 2025-08-26T19:31:41.1092873Z 2025-08-26T19:31:41.1092879Z 2025-08-26T19:31:41.1093164Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-26T19:31:41.1093725Z """ 2025-08-26T19:31:41.1094395Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-26T19:31:41.1095056Z """ 2025-08-26T19:31:41.1095375Z try: 2025-08-26T19:31:41.1095702Z if settings_text: 2025-08-26T19:31:41.1096366Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-26T19:31:41.1097095Z # for easy reading 2025-08-26T19:31:41.1097809Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-26T19:31:41.1098619Z # the backtick character in shell commands. 2025-08-26T19:31:41.1099169Z backtick = chr(96) # backtick character 2025-08-26T19:31:41.1099772Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-26T19:31:41.1100372Z settings = load_yaml(settings_text) 2025-08-26T19:31:41.1100712Z 2025-08-26T19:31:41.1101085Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-26T19:31:41.1101780Z experiments = {} 2025-08-26T19:31:41.1102154Z 2025-08-26T19:31:41.1102510Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-26T19:31:41.1103201Z if not is_valid_experiment_name(exp_name): 2025-08-26T19:31:41.1104202Z # 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-08-26T19:31:41.1105158Z continue 2025-08-26T19:31:41.1105414Z 2025-08-26T19:31:41.1105575Z valid_settings = {} 2025-08-26T19:31:41.1106053Z for setting in exp_settings: 2025-08-26T19:31:41.1106571Z if setting not in Experiment._fields: 2025-08-26T19:31:41.1107066Z log.warning( 2025-08-26T19:31:41.1107707Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-26T19:31:41.1108527Z ) 2025-08-26T19:31:41.1108914Z else: 2025-08-26T19:31:41.1109382Z valid_settings[setting] = exp_settings[setting] 2025-08-26T19:31:41.1109778Z 2025-08-26T19:31:41.1110032Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-26T19:31:41.1110604Z return Settings(experiments) 2025-08-26T19:31:41.1110921Z 2025-08-26T19:31:41.1111077Z except Exception: 2025-08-26T19:31:41.1111508Z log.exception("Failed to parse settings") 2025-08-26T19:31:41.1111856Z 2025-08-26T19:31:41.1112009Z return Settings() 2025-08-26T19:31:41.1112350Z 2025-08-26T19:31:41.1112357Z 2025-08-26T19:31:41.1112583Z def parse_settings(rollout_state: str) -> Settings: 2025-08-26T19:31:41.1113087Z """ 2025-08-26T19:31:41.1113477Z Parse settings, if any, from the rollout state. 2025-08-26T19:31:41.1113840Z 2025-08-26T19:31:41.1114168Z If the issue body contains "---" then the text above that is the settings 2025-08-26T19:31:41.1114856Z and the text below is the list of opted in users. 2025-08-26T19:31:41.1115226Z 2025-08-26T19:31:41.1115589Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-26T19:31:41.1116250Z """ 2025-08-26T19:31:41.1116754Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:41.1117442Z return parse_settings_from_text(settings_text) 2025-08-26T19:31:41.1117804Z 2025-08-26T19:31:41.1117810Z 2025-08-26T19:31:41.1118028Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-26T19:31:41.1118535Z """ 2025-08-26T19:31:41.1118881Z Parse users from the rollout state. 2025-08-26T19:31:41.1119210Z 2025-08-26T19:31:41.1119350Z """ 2025-08-26T19:31:41.1119820Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-26T19:31:41.1120486Z return parse_user_opt_in_from_text(users_text) 2025-08-26T19:31:41.1120846Z 2025-08-26T19:31:41.1120852Z 2025-08-26T19:31:41.1121346Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:41.1122134Z """ 2025-08-26T19:31:41.1122644Z Check if a user is opted into an experiment 2025-08-26T19:31:41.1123128Z """ 2025-08-26T19:31:41.1123533Z return experiment_name in user_optins.get(user, []) 2025-08-26T19:31:41.1123913Z 2025-08-26T19:31:41.1123919Z 2025-08-26T19:31:41.1124299Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-26T19:31:41.1124969Z """ 2025-08-26T19:31:41.1125405Z Check if a user explicitly opted out of an experiment 2025-08-26T19:31:41.1125920Z """ 2025-08-26T19:31:41.1126374Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-26T19:31:41.1126991Z experiment_optout = "-" + experiment_name 2025-08-26T19:31:41.1127578Z if experiment_optout not in user_optins.get(user, []): 2025-08-26T19:31:41.1128130Z return False 2025-08-26T19:31:41.1128361Z 2025-08-26T19:31:41.1128616Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-26T19:31:41.1129150Z log.warning( 2025-08-26T19:31:41.1129878Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-26T19:31:41.1130671Z ) 2025-08-26T19:31:41.1130853Z 2025-08-26T19:31:41.1130997Z return True 2025-08-26T19:31:41.1131207Z 2025-08-26T19:31:41.1131213Z 2025-08-26T19:31:41.1131372Z def get_runner_prefix( 2025-08-26T19:31:41.1131761Z rollout_state: str, 2025-08-26T19:31:41.1132352Z workflow_requestors: Iterable[str], 2025-08-26T19:31:41.1132874Z branch: str, 2025-08-26T19:31:41.1133363Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:41.1133991Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-26T19:31:41.1134518Z is_canary: bool = False, 2025-08-26T19:31:41.1134928Z ) -> str: 2025-08-26T19:31:41.1135475Z settings = parse_settings(rollout_state) 2025-08-26T19:31:41.1136006Z user_optins = parse_users(rollout_state) 2025-08-26T19:31:41.1136341Z 2025-08-26T19:31:41.1136495Z fleet_prefix = "" 2025-08-26T19:31:41.1136884Z prefixes = [] 2025-08-26T19:31:41.1137444Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-26T19:31:41.1138290Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-26T19:31:41.1138930Z log.info( 2025-08-26T19:31:41.1139537Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-26T19:31:41.1140220Z ) 2025-08-26T19:31:41.1140551Z continue 2025-08-26T19:31:41.1140774Z 2025-08-26T19:31:41.1140941Z if opt_out_experiments: 2025-08-26T19:31:41.1141462Z if experiment_name in opt_out_experiments: 2025-08-26T19:31:41.1142164Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-26T19:31:41.1142691Z log.info( 2025-08-26T19:31:41.1143533Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-26T19:31:41.1144413Z ) 2025-08-26T19:31:41.1144761Z continue 2025-08-26T19:31:41.1145006Z 2025-08-26T19:31:41.1145168Z if eligible_experiments: 2025-08-26T19:31:41.1145658Z if experiment_name not in eligible_experiments: 2025-08-26T19:31:41.1146226Z exp_list = ", ".join(eligible_experiments) 2025-08-26T19:31:41.1146722Z log.info( 2025-08-26T19:31:41.1147426Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-26T19:31:41.1148175Z ) 2025-08-26T19:31:41.1148519Z continue 2025-08-26T19:31:41.1148962Z elif not experiment_settings.default: 2025-08-26T19:31:41.1149440Z log.info( 2025-08-26T19:31:41.1150152Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-26T19:31:41.1150861Z ) 2025-08-26T19:31:41.1151221Z continue 2025-08-26T19:31:41.1151441Z 2025-08-26T19:31:41.1151686Z # Is any workflow_requestor opted out to this experiment? 2025-08-26T19:31:41.1152342Z opted_out_users = [ 2025-08-26T19:31:41.1152742Z requestor 2025-08-26T19:31:41.1153151Z for requestor in workflow_requestors 2025-08-26T19:31:41.1153785Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-26T19:31:41.1154342Z ] 2025-08-26T19:31:41.1154531Z 2025-08-26T19:31:41.1154691Z if opted_out_users: 2025-08-26T19:31:41.1155089Z log.info( 2025-08-26T19:31:41.1155645Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-26T19:31:41.1156273Z ) 2025-08-26T19:31:41.1156609Z continue 2025-08-26T19:31:41.1156827Z 2025-08-26T19:31:41.1157087Z # Is any workflow_requestor opted in to this experiment? 2025-08-26T19:31:41.1157626Z opted_in_users = [ 2025-08-26T19:31:41.1158023Z requestor 2025-08-26T19:31:41.1158424Z for requestor in workflow_requestors 2025-08-26T19:31:41.1159023Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-26T19:31:41.1159566Z ] 2025-08-26T19:31:41.1159747Z 2025-08-26T19:31:41.1159895Z enabled = False 2025-08-26T19:31:41.1160281Z if opted_in_users: 2025-08-26T19:31:41.1160666Z log.info( 2025-08-26T19:31:41.1161192Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-26T19:31:41.1161802Z ) 2025-08-26T19:31:41.1162358Z enabled = True 2025-08-26T19:31:41.1162623Z 2025-08-26T19:31:41.1162823Z elif experiment_settings.rollout_perc: 2025-08-26T19:31:41.1163578Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-26T19:31:41.1164564Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-26T19:31:41.1165148Z log.info( 2025-08-26T19:31:41.1165933Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-26T19:31:41.1166748Z ) 2025-08-26T19:31:41.1167121Z enabled = True 2025-08-26T19:31:41.1167387Z 2025-08-26T19:31:41.1167529Z if enabled: 2025-08-26T19:31:41.1167908Z label = experiment_name 2025-08-26T19:31:41.1168397Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-26T19:31:41.1169143Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-26T19:31:41.1169937Z # - If it's enabled, then we always list it's prefix first 2025-08-26T19:31:41.1170616Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-26T19:31:41.1171201Z if is_canary: 2025-08-26T19:31:41.1171635Z label += CANARY_FLEET_SUFFIX 2025-08-26T19:31:41.1172246Z fleet_prefix = label 2025-08-26T19:31:41.1172681Z else: 2025-08-26T19:31:41.1173062Z prefixes.append(label) 2025-08-26T19:31:41.1173371Z 2025-08-26T19:31:41.1173563Z if len(prefixes) > 1: 2025-08-26T19:31:41.1173951Z log.error( 2025-08-26T19:31:41.1175146Z 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-08-26T19:31:41.1176168Z ) 2025-08-26T19:31:41.1176516Z prefixes = prefixes[:1] 2025-08-26T19:31:41.1176798Z 2025-08-26T19:31:41.1176972Z # Fleet always comes first 2025-08-26T19:31:41.1177395Z if fleet_prefix: 2025-08-26T19:31:41.1177799Z prefixes.insert(0, fleet_prefix) 2025-08-26T19:31:41.1178131Z 2025-08-26T19:31:41.1178495Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-26T19:31:41.1178872Z 2025-08-26T19:31:41.1178878Z 2025-08-26T19:31:41.1179279Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-26T19:31:41.1179974Z """ 2025-08-26T19:31:41.1180502Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-26T19:31:41.1181008Z 2025-08-26T19:31:41.1181363Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-26T19:31:41.1181989Z """ 2025-08-26T19:31:41.1182465Z gh = get_gh_client(github_token) 2025-08-26T19:31:41.1182943Z issue = get_issue(gh, repo, issue_num) 2025-08-26T19:31:41.1183515Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-26T19:31:41.1183907Z 2025-08-26T19:31:41.1183914Z 2025-08-26T19:31:41.1184272Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-26T19:31:41.1184969Z for _ in range(num_retries): 2025-08-26T19:31:41.1185398Z try: 2025-08-26T19:31:41.1185781Z req = Request(url=url, headers=headers) 2025-08-26T19:31:41.1186375Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-26T19:31:41.1186949Z return json.loads(content) 2025-08-26T19:31:41.1187418Z except Exception as e: 2025-08-26T19:31:41.1187891Z log.warning(f"Could not download {url}: {e}") 2025-08-26T19:31:41.1188258Z 2025-08-26T19:31:41.1188601Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-26T19:31:41.1189233Z return {} 2025-08-26T19:31:41.1189436Z 2025-08-26T19:31:41.1189442Z 2025-08-26T19:31:41.1189580Z @cache 2025-08-26T19:31:41.1190132Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-26T19:31:41.1190803Z """ 2025-08-26T19:31:41.1191148Z Dynamically get PR information 2025-08-26T19:31:41.1191703Z """ 2025-08-26T19:31:41.1192245Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-26T19:31:41.1192814Z headers = { 2025-08-26T19:31:41.1193234Z "Accept": "application/vnd.github.v3+json", 2025-08-26T19:31:41.1193772Z "Authorization": f"token {github_token}", 2025-08-26T19:31:41.1194310Z } 2025-08-26T19:31:41.1194745Z json_response: dict[str, Any] = download_json( 2025-08-26T19:31:41.1195284Z url=f"{github_api}/issues/{pr_number}", 2025-08-26T19:31:41.1195773Z headers=headers, 2025-08-26T19:31:41.1196151Z ) 2025-08-26T19:31:41.1196337Z 2025-08-26T19:31:41.1196509Z if not json_response: 2025-08-26T19:31:41.1197012Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-26T19:31:41.1197561Z return {} 2025-08-26T19:31:41.1197775Z 2025-08-26T19:31:41.1197931Z return json_response 2025-08-26T19:31:41.1198178Z 2025-08-26T19:31:41.1198183Z 2025-08-26T19:31:41.1198545Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-26T19:31:41.1199213Z """ 2025-08-26T19:31:41.1199683Z Dynamically get the latest list of labels from the pull request 2025-08-26T19:31:41.1200267Z """ 2025-08-26T19:31:41.1200689Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-26T19:31:41.1201237Z return { 2025-08-26T19:31:41.1201750Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-26T19:31:41.1202602Z } 2025-08-26T19:31:41.1202783Z 2025-08-26T19:31:41.1202789Z 2025-08-26T19:31:41.1202949Z def main() -> None: 2025-08-26T19:31:41.1203319Z args = parse_args() 2025-08-26T19:31:41.1203556Z 2025-08-26T19:31:41.1203758Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-26T19:31:41.1204102Z 2025-08-26T19:31:41.1204273Z # Check if the PR is opt-out 2025-08-26T19:31:41.1204710Z if args.pr_number: 2025-08-26T19:31:41.1205287Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-26T19:31:41.1206098Z if OPT_OUT_LABEL in labels: 2025-08-26T19:31:41.1206548Z log.info( 2025-08-26T19:31:41.1207174Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-26T19:31:41.1207862Z ) 2025-08-26T19:31:41.1208351Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:41.1208955Z sys.exit() 2025-08-26T19:31:41.1209183Z 2025-08-26T19:31:41.1209327Z try: 2025-08-26T19:31:41.1209713Z rollout_state = get_rollout_state_from_issue( 2025-08-26T19:31:41.1210339Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-26T19:31:41.1210909Z ) 2025-08-26T19:31:41.1211091Z 2025-08-26T19:31:41.1211279Z username = get_potential_pr_author( 2025-08-26T19:31:41.1211763Z args.github_token, 2025-08-26T19:31:41.1212731Z args.github_repo, 2025-08-26T19:31:41.1213155Z args.github_actor, 2025-08-26T19:31:41.1213588Z args.github_ref_type, 2025-08-26T19:31:41.1214037Z args.github_branch, 2025-08-26T19:31:41.1214447Z ) 2025-08-26T19:31:41.1214630Z 2025-08-26T19:31:41.1214885Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-26T19:31:41.1215301Z 2025-08-26T19:31:41.1215490Z runner_label_prefix = get_runner_prefix( 2025-08-26T19:31:41.1215986Z rollout_state, 2025-08-26T19:31:41.1216414Z (args.github_issue_owner, username), 2025-08-26T19:31:41.1216908Z args.github_branch, 2025-08-26T19:31:41.1217351Z args.eligible_experiments, 2025-08-26T19:31:41.1217830Z args.opt_out_experiments, 2025-08-26T19:31:41.1218270Z is_canary, 2025-08-26T19:31:41.1218635Z ) 2025-08-26T19:31:41.1218814Z 2025-08-26T19:31:41.1218977Z except Exception as e: 2025-08-26T19:31:41.1219379Z log.error( 2025-08-26T19:31:41.1219981Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-26T19:31:41.1220800Z ) 2025-08-26T19:31:41.1220980Z 2025-08-26T19:31:41.1221280Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-26T19:31:41.1221725Z 2025-08-26T19:31:41.1221731Z 2025-08-26T19:31:41.1221885Z if __name__ == "__main__": 2025-08-26T19:31:41.1222384Z main() 2025-08-26T19:31:41.1222567Z 2025-08-26T19:31:41.1312610Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:31:41.1313486Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-26T19:31:41.1353842Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:41.1354280Z env: 2025-08-26T19:31:41.1354855Z GITHUB_TOKEN: *** 2025-08-26T19:31:41.1355240Z ISSUE_NUMBER: 5132 2025-08-26T19:31:41.1355652Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:41.1356108Z ISSUE_OWNER: 2025-08-26T19:31:41.1356475Z CHECK_EXPERIMENTS: 2025-08-26T19:31:41.1356856Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:31:41.1357248Z PR_NUMBER: 2025-08-26T19:31:41.1357580Z ##[endgroup] 2025-08-26T19:31:43.4658971Z Defaulting to user installation because normal site-packages is not writeable 2025-08-26T19:31:45.0541213Z Collecting urllib3==1.26.18 2025-08-26T19:31:45.1353580Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-26T19:31:45.1685676Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.0 MB/s eta 0:00:00 2025-08-26T19:31:45.1919469Z Collecting PyGithub==2.3.0 2025-08-26T19:31:45.2109364Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-26T19:31:45.2529886Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-26T19:31:45.2720881Z 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-08-26T19:31:45.2793373Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-26T19:31:45.2812549Z 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-08-26T19:31:45.2827677Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-26T19:31:45.3090671Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-26T19:31:45.3280167Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-26T19:31:45.3511066Z 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-08-26T19:31:45.4619556Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:31:45.4811995Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-26T19:31:45.6003597Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-26T19:31:45.6196148Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-08-26T19:31:45.6380200Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-26T19:31:45.6569087Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-26T19:31:45.6953174Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-26T19:31:45.7186421Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 6.3 MB/s eta 0:00:00 2025-08-26T19:31:45.7378051Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-26T19:31:45.7641619Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 14.1 MB/s eta 0:00:00 2025-08-26T19:31:45.7830547Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-08-26T19:31:45.8071572Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 38.1 MB/s eta 0:00:00 2025-08-26T19:31:45.8263744Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-26T19:31:45.8474711Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-26T19:31:45.8579263Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 54.3 MB/s eta 0:00:00 2025-08-26T19:31:45.8770389Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-08-26T19:31:45.8813691Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 29.4 MB/s eta 0:00:00 2025-08-26T19:31:45.9003808Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-26T19:31:45.9048018Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 38.9 MB/s eta 0:00:00 2025-08-26T19:31:46.2448911Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-26T19:31:46.7790201Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-08-26T19:31:46.8761080Z ##[group]Run curr_branch="main" 2025-08-26T19:31:46.8761390Z curr_branch="main" 2025-08-26T19:31:46.8761637Z curr_ref_type="branch" 2025-08-26T19:31:46.8761886Z echo "Current branch is '$curr_branch'" 2025-08-26T19:31:46.8762358Z  2025-08-26T19:31:46.8762551Z python3 runner_determinator.py \ 2025-08-26T19:31:46.8762834Z  --github-token "$GITHUB_TOKEN" \ 2025-08-26T19:31:46.8763114Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-26T19:31:46.8763377Z  --github-branch "$curr_branch" \ 2025-08-26T19:31:46.8763634Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-26T19:31:46.8763919Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-26T19:31:46.8764194Z  --github-ref-type "$curr_ref_type" \ 2025-08-26T19:31:46.8764472Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-26T19:31:46.8764774Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-26T19:31:46.8765141Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-26T19:31:46.8765427Z  --pr-number "${PR_NUMBER}" 2025-08-26T19:31:46.8806458Z shell: /usr/bin/bash -e {0} 2025-08-26T19:31:46.8806696Z env: 2025-08-26T19:31:46.8807171Z GITHUB_TOKEN: *** 2025-08-26T19:31:46.8807366Z ISSUE_NUMBER: 5132 2025-08-26T19:31:46.8807566Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-26T19:31:46.8807808Z ISSUE_OWNER: 2025-08-26T19:31:46.8807980Z CHECK_EXPERIMENTS: 2025-08-26T19:31:46.8808175Z OPT_OUT_EXPERIMENTS: 2025-08-26T19:31:46.8808392Z PR_NUMBER: 2025-08-26T19:31:46.8808559Z ##[endgroup] 2025-08-26T19:31:46.8869465Z Current branch is 'main' 2025-08-26T19:31:48.4493041Z INFO : Based on rollout percentage of 75%, enabling experiment lf. 2025-08-26T19:31:48.4494279Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-26T19:31:48.4495274Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-26T19:31:48.4495964Z INFO : Setting output: label-type='lf.' 2025-08-26T19:31:48.4824816Z Evaluate and set job outputs 2025-08-26T19:31:48.4831418Z Set output 'label-type' 2025-08-26T19:31:48.4833559Z Cleaning up orphan processes