2025-06-01T20:56:27.5949059Z Current runner version: '2.324.0' 2025-06-01T20:56:27.5977768Z ##[group]Operating System 2025-06-01T20:56:27.5978561Z Ubuntu 2025-06-01T20:56:27.5979096Z 24.04.2 2025-06-01T20:56:27.5979589Z LTS 2025-06-01T20:56:27.5980021Z ##[endgroup] 2025-06-01T20:56:27.5980575Z ##[group]Runner Image 2025-06-01T20:56:27.5981185Z Image: ubuntu-24.04 2025-06-01T20:56:27.5981672Z Version: 20250527.1.0 2025-06-01T20:56:27.5982901Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250527.1/images/ubuntu/Ubuntu2404-Readme.md 2025-06-01T20:56:27.5984293Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250527.1 2025-06-01T20:56:27.5985175Z ##[endgroup] 2025-06-01T20:56:27.5985636Z ##[group]Runner Image Provisioner 2025-06-01T20:56:27.5986312Z 2.0.437.1 2025-06-01T20:56:27.5986768Z ##[endgroup] 2025-06-01T20:56:27.5987789Z ##[group]GITHUB_TOKEN Permissions 2025-06-01T20:56:27.5989845Z Contents: read 2025-06-01T20:56:27.5990398Z Metadata: read 2025-06-01T20:56:27.5991109Z Packages: read 2025-06-01T20:56:27.5991670Z ##[endgroup] 2025-06-01T20:56:27.5994108Z Secret source: Actions 2025-06-01T20:56:27.5994945Z Prepare workflow directory 2025-06-01T20:56:27.6525480Z Prepare all required actions 2025-06-01T20:56:27.6580245Z Complete job name: get-label-type / runner-determinator 2025-06-01T20:56:27.7194555Z ##[group]Run cat < runner_determinator.py 2025-06-01T20:56:27.7197014Z cat < runner_determinator.py 2025-06-01T20:56:27.7197743Z # flake8: noqa: G004 2025-06-01T20:56:27.7198253Z  2025-06-01T20:56:27.7198979Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:27.7200522Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:27.7201422Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:27.7202256Z  2025-06-01T20:56:27.7202768Z """ 2025-06-01T20:56:27.7203605Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:27.7204626Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:27.7205802Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:27.7206711Z of which runners should be used to run which job. 2025-06-01T20:56:27.7207413Z  2025-06-01T20:56:27.7208083Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:27.7209075Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:27.7210118Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:27.7210912Z list, defined. 2025-06-01T20:56:27.7211376Z  2025-06-01T20:56:27.7212088Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:27.7213473Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:27.7214457Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:27.7215252Z  2025-06-01T20:56:27.7215904Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:27.7216872Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:27.7217771Z experiments which the user could be opted in to. 2025-06-01T20:56:27.7218429Z  2025-06-01T20:56:27.7218919Z The user list has the following rules: 2025-06-01T20:56:27.7219586Z  2025-06-01T20:56:27.7220461Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:27.7221368Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:27.7222639Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:27.7223557Z  2025-06-01T20:56:27.7224036Z Example config: 2025-06-01T20:56:27.7224693Z  # A list of experiments that can be opted into. 2025-06-01T20:56:27.7225482Z  # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:27.7226195Z  # Expected syntax is: 2025-06-01T20:56:27.7227001Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:27.7228072Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:27.7228902Z  2025-06-01T20:56:27.7229411Z  experiments: 2025-06-01T20:56:27.7229925Z  lf: 2025-06-01T20:56:27.7230379Z  rollout_percent: 25 2025-06-01T20:56:27.7231010Z  all_branches: false 2025-06-01T20:56:27.7231547Z  default: true 2025-06-01T20:56:27.7232288Z  --- 2025-06-01T20:56:27.7232856Z  2025-06-01T20:56:27.7233325Z  # Opt-ins: 2025-06-01T20:56:27.7234042Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:27.7235183Z  # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:27.7236099Z  # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:27.7236877Z  # Experiments should be from the above list. 2025-06-01T20:56:27.7237572Z  2025-06-01T20:56:27.7238024Z  @User1,-lf,split_build 2025-06-01T20:56:27.7238548Z  @User2,lf 2025-06-01T20:56:27.7239097Z  @User3,split_build 2025-06-01T20:56:27.7239590Z """ 2025-06-01T20:56:27.7240059Z  2025-06-01T20:56:27.7240500Z import json 2025-06-01T20:56:27.7241006Z import logging 2025-06-01T20:56:27.7241484Z import os 2025-06-01T20:56:27.7242019Z import random 2025-06-01T20:56:27.7242683Z import re 2025-06-01T20:56:27.7243159Z import sys 2025-06-01T20:56:27.7243724Z from argparse import ArgumentParser 2025-06-01T20:56:27.7244354Z from collections.abc import Iterable 2025-06-01T20:56:27.7244988Z from functools import cache 2025-06-01T20:56:27.7245608Z from logging import LogRecord 2025-06-01T20:56:27.7246270Z from typing import Any, NamedTuple 2025-06-01T20:56:27.7246954Z from urllib.request import Request, urlopen 2025-06-01T20:56:27.7247622Z  2025-06-01T20:56:27.7248090Z import yaml 2025-06-01T20:56:27.7248579Z from github import Auth, Github 2025-06-01T20:56:27.7249285Z from github.Issue import Issue 2025-06-01T20:56:27.7249928Z  2025-06-01T20:56:27.7250333Z  2025-06-01T20:56:27.7250891Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:27.7251705Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:27.7253302Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:27.7254264Z  2025-06-01T20:56:27.7254801Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:27.7255519Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:27.7256157Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:27.7256853Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:27.7257433Z  2025-06-01T20:56:27.7257959Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:27.7258535Z  2025-06-01T20:56:27.7259026Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:27.7259640Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:27.7260179Z  2025-06-01T20:56:27.7260654Z  2025-06-01T20:56:27.7261071Z class Experiment(NamedTuple): 2025-06-01T20:56:27.7261989Z  rollout_perc: float = ( 2025-06-01T20:56:27.7262889Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:27.7263650Z  ) 2025-06-01T20:56:27.7264202Z  all_branches: bool = ( 2025-06-01T20:56:27.7264944Z  False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:27.7265745Z  ) 2025-06-01T20:56:27.7266244Z  default: bool = ( 2025-06-01T20:56:27.7266952Z  True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:27.7267740Z  ) 2025-06-01T20:56:27.7268184Z  2025-06-01T20:56:27.7268645Z  # Add more fields as needed 2025-06-01T20:56:27.7269159Z  2025-06-01T20:56:27.7269685Z  2025-06-01T20:56:27.7270154Z class Settings(NamedTuple): 2025-06-01T20:56:27.7270711Z  """ 2025-06-01T20:56:27.7271332Z  Settings for the experiments that can be opted into. 2025-06-01T20:56:27.7272022Z  """ 2025-06-01T20:56:27.7272687Z  2025-06-01T20:56:27.7273282Z  experiments: dict[str, Experiment] = {} 2025-06-01T20:56:27.7273905Z  2025-06-01T20:56:27.7274441Z  2025-06-01T20:56:27.7275046Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:27.7275772Z  """Color codes the log messages based on the log level""" 2025-06-01T20:56:27.7276435Z  2025-06-01T20:56:27.7276947Z  COLORS = { 2025-06-01T20:56:27.7277488Z  "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:27.7278089Z  "ERROR": "\033[31m", # Red 2025-06-01T20:56:27.7278731Z  "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:27.7279345Z  "INFO": "\033[0m", # Reset 2025-06-01T20:56:27.7279915Z  "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:27.7280575Z  } 2025-06-01T20:56:27.7280998Z  2025-06-01T20:56:27.7281514Z  def format(self, record: LogRecord) -> str: 2025-06-01T20:56:27.7282694Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:27.7283571Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:27.7284260Z  return super().format(record) 2025-06-01T20:56:27.7284921Z  2025-06-01T20:56:27.7285349Z  2025-06-01T20:56:27.7285795Z handler = logging.StreamHandler() 2025-06-01T20:56:27.7286705Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:27.7287528Z  2025-06-01T20:56:27.7288053Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:27.7288835Z log.addHandler(handler) 2025-06-01T20:56:27.7289373Z log.setLevel(logging.INFO) 2025-06-01T20:56:27.7289917Z  2025-06-01T20:56:27.7290423Z  2025-06-01T20:56:27.7290941Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:27.7291592Z  """ 2025-06-01T20:56:27.7292562Z  Defines outputs of the github action that invokes this script 2025-06-01T20:56:27.7293348Z  """ 2025-06-01T20:56:27.7293802Z  if not GITHUB_OUTPUT: 2025-06-01T20:56:27.7295051Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:27.7296239Z  log.warning( 2025-06-01T20:56:27.7297163Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-06-01T20:56:27.7298239Z  ) 2025-06-01T20:56:27.7298760Z  print(f"::set-output name={key}::{value}") 2025-06-01T20:56:27.7299406Z  return 2025-06-01T20:56:27.7299977Z  2025-06-01T20:56:27.7300601Z  with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:27.7301327Z  log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:27.7302067Z  f.write(f"{key}={value}\n") 2025-06-01T20:56:27.7303057Z  2025-06-01T20:56:27.7303480Z  2025-06-01T20:56:27.7304167Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:27.7304916Z  return frozenset( 2025-06-01T20:56:27.7305661Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:27.7306489Z  ) 2025-06-01T20:56:27.7306919Z  2025-06-01T20:56:27.7307346Z  2025-06-01T20:56:27.7307802Z def parse_args() -> Any: 2025-06-01T20:56:27.7308530Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:27.7309529Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:27.7310437Z  parser.add_argument( 2025-06-01T20:56:27.7311022Z  "--github-issue-repo", 2025-06-01T20:56:27.7311569Z  type=str, 2025-06-01T20:56:27.7312346Z  required=False, 2025-06-01T20:56:27.7313093Z  default="pytorch/test-infra", 2025-06-01T20:56:27.7313818Z  help="GitHub repo to get the issue", 2025-06-01T20:56:27.7314491Z  ) 2025-06-01T20:56:27.7314941Z  parser.add_argument( 2025-06-01T20:56:27.7315516Z  "--github-repo", 2025-06-01T20:56:27.7316326Z  type=str, 2025-06-01T20:56:27.7316881Z  required=True, 2025-06-01T20:56:27.7317477Z  help="GitHub repo where CI is running", 2025-06-01T20:56:27.7318127Z  ) 2025-06-01T20:56:27.7318592Z  parser.add_argument( 2025-06-01T20:56:27.7319336Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:27.7320190Z  ) 2025-06-01T20:56:27.7320636Z  parser.add_argument( 2025-06-01T20:56:27.7321429Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:27.7322324Z  ) 2025-06-01T20:56:27.7322847Z  parser.add_argument( 2025-06-01T20:56:27.7323619Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:27.7324400Z  ) 2025-06-01T20:56:27.7324898Z  parser.add_argument( 2025-06-01T20:56:27.7325681Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:27.7326502Z  ) 2025-06-01T20:56:27.7326970Z  parser.add_argument( 2025-06-01T20:56:27.7327542Z  "--github-ref-type", 2025-06-01T20:56:27.7328141Z  type=str, 2025-06-01T20:56:27.7328651Z  required=True, 2025-06-01T20:56:27.7329314Z  help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:27.7329907Z  ) 2025-06-01T20:56:27.7330451Z  parser.add_argument( 2025-06-01T20:56:27.7331060Z  "--eligible-experiments", 2025-06-01T20:56:27.7396609Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:27.7397711Z  required=False, 2025-06-01T20:56:27.7398480Z  default="", 2025-06-01T20:56:27.7400008Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:27.7401706Z  ) 2025-06-01T20:56:27.7402370Z  parser.add_argument( 2025-06-01T20:56:27.7402904Z  "--opt-out-experiments", 2025-06-01T20:56:27.7403457Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:27.7403977Z  required=False, 2025-06-01T20:56:27.7404714Z  default="", 2025-06-01T20:56:27.7405143Z  help=( 2025-06-01T20:56:27.7405839Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:27.7406964Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:27.7407773Z  ), 2025-06-01T20:56:27.7408151Z  ) 2025-06-01T20:56:27.7408527Z  parser.add_argument( 2025-06-01T20:56:27.7408993Z  "--pr-number", 2025-06-01T20:56:27.7409440Z  type=str, 2025-06-01T20:56:27.7409872Z  required=False, 2025-06-01T20:56:27.7410327Z  default="", 2025-06-01T20:56:27.7410853Z  help="the optional PR number where this is run", 2025-06-01T20:56:27.7411410Z  ) 2025-06-01T20:56:27.7411770Z  2025-06-01T20:56:27.7413065Z  return parser.parse_args() 2025-06-01T20:56:27.7413655Z  2025-06-01T20:56:27.7414017Z  2025-06-01T20:56:27.7414627Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.7415550Z  auth = Auth.Token(github_token) 2025-06-01T20:56:27.7416091Z  return Github(auth=auth) 2025-06-01T20:56:27.7416554Z  2025-06-01T20:56:27.7416889Z  2025-06-01T20:56:27.7417526Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.7418324Z  repo = gh.get_repo(repo) 2025-06-01T20:56:27.7418865Z  return repo.get_issue(number=issue_num) 2025-06-01T20:56:27.7419380Z  2025-06-01T20:56:27.7419715Z  2025-06-01T20:56:27.7420073Z def get_potential_pr_author( 2025-06-01T20:56:27.7420749Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:27.7421411Z ) -> str: 2025-06-01T20:56:27.7421977Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:27.7423008Z  # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:27.7423785Z  # embedded in the tag name: ciflow// 2025-06-01T20:56:27.7424352Z  2025-06-01T20:56:27.7424730Z  gh = get_gh_client(github_token) 2025-06-01T20:56:27.7425219Z  2025-06-01T20:56:27.7425681Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:27.7426319Z  split_tag = ref_name.split("/") 2025-06-01T20:56:27.7426818Z  if ( 2025-06-01T20:56:27.7427226Z  len(split_tag) == 3 2025-06-01T20:56:27.7427733Z  and split_tag[0] == "ciflow" 2025-06-01T20:56:27.7428262Z  and split_tag[2].isnumeric() 2025-06-01T20:56:27.7428767Z  ): 2025-06-01T20:56:27.7429174Z  pr_number = split_tag[2] 2025-06-01T20:56:27.7429676Z  try: 2025-06-01T20:56:27.7430136Z  repository = gh.get_repo(repo) 2025-06-01T20:56:27.7430765Z  pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:27.7431375Z  except Exception as e: 2025-06-01T20:56:27.7431908Z  raise Exception( # noqa: TRY002 2025-06-01T20:56:27.7432904Z  f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:27.7433557Z  ) from e 2025-06-01T20:56:27.7434126Z  return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:27.7434831Z  # In all other cases, return the original input username 2025-06-01T20:56:27.7435429Z  return username 2025-06-01T20:56:27.7435851Z  2025-06-01T20:56:27.7436334Z  2025-06-01T20:56:27.7436758Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:27.7437285Z  """ 2025-06-01T20:56:27.7437947Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:27.7438705Z  """ 2025-06-01T20:56:27.7439256Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:27.7439907Z  2025-06-01T20:56:27.7440230Z  2025-06-01T20:56:27.7440617Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:27.7441105Z  try: 2025-06-01T20:56:27.7441513Z  data = yaml.safe_load(yaml_text) 2025-06-01T20:56:27.7442017Z  return data 2025-06-01T20:56:27.7443065Z  except yaml.YAMLError: 2025-06-01T20:56:27.7443590Z  log.exception("Error loading YAML") 2025-06-01T20:56:27.7444106Z  raise 2025-06-01T20:56:27.7444511Z  2025-06-01T20:56:27.7444839Z  2025-06-01T20:56:27.7445447Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:27.7446169Z  """ 2025-06-01T20:56:27.7446950Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:27.7447701Z  2025-06-01T20:56:27.7448243Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.7449007Z  and the text below is the list of opted in users. 2025-06-01T20:56:27.7449550Z  2025-06-01T20:56:27.7450118Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:27.7450790Z  """ 2025-06-01T20:56:27.7451253Z  rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:27.7451846Z  if len(rollout_state_parts) >= 2: 2025-06-01T20:56:27.7452654Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:27.7453239Z  else: 2025-06-01T20:56:27.7453640Z  return "", rollout_state 2025-06-01T20:56:27.7454112Z  2025-06-01T20:56:27.7454439Z  2025-06-01T20:56:27.7454840Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:27.7455352Z  """ 2025-06-01T20:56:27.7455896Z  Dictionary of users with a list of features they have opted into 2025-06-01T20:56:27.7456524Z  """ 2025-06-01T20:56:27.7456878Z  2025-06-01T20:56:27.7457210Z  2025-06-01T20:56:27.7457738Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:27.7458397Z  """ 2025-06-01T20:56:27.7459113Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-06-01T20:56:27.7459919Z  2025-06-01T20:56:27.7460745Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:27.7461729Z  - Example line: "@User1,lf,split_build" 2025-06-01T20:56:27.7462759Z  - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:27.7463396Z  2025-06-01T20:56:27.7463729Z  2025-06-01T20:56:27.7464056Z  """ 2025-06-01T20:56:27.7464435Z  optins = UserOptins() 2025-06-01T20:56:27.7464954Z  for user in user_optin_text.split("\n"): 2025-06-01T20:56:27.7465519Z  user = user.strip("\r\n\t -") 2025-06-01T20:56:27.7466083Z  if not user or not user.startswith("@"): 2025-06-01T20:56:27.7466637Z  # Not a valid user. Skip 2025-06-01T20:56:27.7467130Z  continue 2025-06-01T20:56:27.7467536Z  2025-06-01T20:56:27.7467873Z  if user: 2025-06-01T20:56:27.7468482Z  usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:27.7469189Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:27.7469815Z  2025-06-01T20:56:27.7470155Z  return optins 2025-06-01T20:56:27.7470563Z  2025-06-01T20:56:27.7470877Z  2025-06-01T20:56:27.7471371Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:27.7471966Z  """ 2025-06-01T20:56:27.7472635Z  Check if the experiment name is valid. 2025-06-01T20:56:27.7473160Z  A valid name: 2025-06-01T20:56:27.7473842Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:27.7474769Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:27.7475468Z  - Cannot contain spaces 2025-06-01T20:56:27.7475948Z  """ 2025-06-01T20:56:27.7476292Z  2025-06-01T20:56:27.7476743Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:27.7477436Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:27.7478141Z  2025-06-01T20:56:27.7478484Z  if valid: 2025-06-01T20:56:27.7478882Z  return True 2025-06-01T20:56:27.7479298Z  2025-06-01T20:56:27.7479636Z  log.error( 2025-06-01T20:56:27.7481058Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-06-01T20:56:27.7482646Z  ) 2025-06-01T20:56:27.7483012Z  return False 2025-06-01T20:56:27.7483407Z  2025-06-01T20:56:27.7483727Z  2025-06-01T20:56:27.7484233Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:27.7484847Z  """ 2025-06-01T20:56:27.7485442Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:27.7486126Z  """ 2025-06-01T20:56:27.7486476Z  try: 2025-06-01T20:56:27.7486834Z  if settings_text: 2025-06-01T20:56:27.7487577Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:27.7488343Z  # for easy reading 2025-06-01T20:56:27.7489135Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:27.7490011Z  # the backtick character in shell commands. 2025-06-01T20:56:27.7490609Z  backtick = chr(96) # backtick character 2025-06-01T20:56:27.7491278Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:27.7491937Z  settings = load_yaml(settings_text) 2025-06-01T20:56:27.7492550Z  2025-06-01T20:56:27.7493138Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:27.7493857Z  experiments = {} 2025-06-01T20:56:27.7494310Z  2025-06-01T20:56:27.7494855Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:27.7495599Z  if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:27.7496673Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-06-01T20:56:27.7497662Z  continue 2025-06-01T20:56:27.7498105Z  2025-06-01T20:56:27.7498466Z  valid_settings = {} 2025-06-01T20:56:27.7499126Z  for setting in exp_settings: 2025-06-01T20:56:27.7499693Z  if setting not in Experiment._fields: 2025-06-01T20:56:27.7500239Z  log.warning( 2025-06-01T20:56:27.7500950Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:27.7501635Z  ) 2025-06-01T20:56:27.7502091Z  else: 2025-06-01T20:56:27.7502927Z  valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:27.7503494Z  2025-06-01T20:56:27.7503969Z  experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:27.7504597Z  return Settings(experiments) 2025-06-01T20:56:27.7505087Z  2025-06-01T20:56:27.7505431Z  except Exception: 2025-06-01T20:56:27.7505949Z  log.exception("Failed to parse settings") 2025-06-01T20:56:27.7506473Z  2025-06-01T20:56:27.7506826Z  return Settings() 2025-06-01T20:56:27.7507258Z  2025-06-01T20:56:27.7507581Z  2025-06-01T20:56:27.7508173Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:27.7508748Z  """ 2025-06-01T20:56:27.7509207Z  Parse settings, if any, from the rollout state. 2025-06-01T20:56:27.7509745Z  2025-06-01T20:56:27.7510277Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.7511049Z  and the text below is the list of opted in users. 2025-06-01T20:56:27.7511585Z  2025-06-01T20:56:27.7512400Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:27.7513131Z  """ 2025-06-01T20:56:27.7513708Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.7514486Z  return parse_settings_from_text(settings_text) 2025-06-01T20:56:27.7515023Z  2025-06-01T20:56:27.7515351Z  2025-06-01T20:56:27.7515791Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:27.7516353Z  """ 2025-06-01T20:56:27.7516755Z  Parse users from the rollout state. 2025-06-01T20:56:27.7517253Z  2025-06-01T20:56:27.7517570Z  """ 2025-06-01T20:56:27.7518118Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.7518855Z  return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:27.7519390Z  2025-06-01T20:56:27.7519708Z  2025-06-01T20:56:27.7520313Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.7521037Z  """ 2025-06-01T20:56:27.7521469Z  Check if a user is opted into an experiment 2025-06-01T20:56:27.7521995Z  """ 2025-06-01T20:56:27.7522596Z  return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:27.7523153Z  2025-06-01T20:56:27.7523476Z  2025-06-01T20:56:27.7524087Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.7524811Z  """ 2025-06-01T20:56:27.7525280Z  Check if a user explicitly opted out of an experiment 2025-06-01T20:56:27.7525848Z  """ 2025-06-01T20:56:27.7526364Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:27.7527053Z  experiment_optout = "-" + experiment_name 2025-06-01T20:56:27.7527699Z  if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:27.7528295Z  return False 2025-06-01T20:56:27.7528712Z  2025-06-01T20:56:27.7529311Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:27.7529907Z  log.warning( 2025-06-01T20:56:27.7530707Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:27.7531549Z  ) 2025-06-01T20:56:27.7531904Z  2025-06-01T20:56:27.7532356Z  return True 2025-06-01T20:56:27.7532750Z  2025-06-01T20:56:27.7533069Z  2025-06-01T20:56:27.7533416Z def get_runner_prefix( 2025-06-01T20:56:27.7533864Z  rollout_state: str, 2025-06-01T20:56:27.7534351Z  workflow_requestors: Iterable[str], 2025-06-01T20:56:27.7534849Z  branch: str, 2025-06-01T20:56:27.7535377Z  eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.7536055Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.7536633Z  is_canary: bool = False, 2025-06-01T20:56:27.7537095Z ) -> str: 2025-06-01T20:56:27.7537526Z  settings = parse_settings(rollout_state) 2025-06-01T20:56:27.7538114Z  user_optins = parse_users(rollout_state) 2025-06-01T20:56:27.7538616Z  2025-06-01T20:56:27.7539083Z  fleet_prefix = "" 2025-06-01T20:56:27.7539535Z  prefixes = [] 2025-06-01T20:56:27.7540190Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:27.7541121Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:27.7541813Z  log.info( 2025-06-01T20:56:27.7542831Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:27.7543567Z  ) 2025-06-01T20:56:27.7543969Z  continue 2025-06-01T20:56:27.7544383Z  2025-06-01T20:56:27.7544751Z  if opt_out_experiments: 2025-06-01T20:56:27.7545301Z  if experiment_name in opt_out_experiments: 2025-06-01T20:56:27.7545936Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:27.7546509Z  log.info( 2025-06-01T20:56:27.7547422Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:27.7548353Z  ) 2025-06-01T20:56:27.7548787Z  continue 2025-06-01T20:56:27.7549215Z  2025-06-01T20:56:27.7549580Z  if eligible_experiments: 2025-06-01T20:56:27.7550159Z  if experiment_name not in eligible_experiments: 2025-06-01T20:56:27.7550790Z  exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:27.7551333Z  log.info( 2025-06-01T20:56:27.7552275Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:27.7553226Z  ) 2025-06-01T20:56:27.7553645Z  continue 2025-06-01T20:56:27.7554148Z  elif not experiment_settings.default: 2025-06-01T20:56:27.7554666Z  log.info( 2025-06-01T20:56:27.7555342Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:27.7556045Z  ) 2025-06-01T20:56:27.7556430Z  continue 2025-06-01T20:56:27.7556845Z  2025-06-01T20:56:27.7557305Z  # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:27.7557910Z  opted_out_users = [ 2025-06-01T20:56:27.7558376Z  requestor 2025-06-01T20:56:27.7558861Z  for requestor in workflow_requestors 2025-06-01T20:56:27.7559694Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.7560345Z  ] 2025-06-01T20:56:27.7560713Z  2025-06-01T20:56:27.7561055Z  if opted_out_users: 2025-06-01T20:56:27.7561517Z  log.info( 2025-06-01T20:56:27.7562297Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:27.7562978Z  ) 2025-06-01T20:56:27.7563363Z  continue 2025-06-01T20:56:27.7563770Z  2025-06-01T20:56:27.7564226Z  # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:27.7564821Z  opted_in_users = [ 2025-06-01T20:56:27.7565286Z  requestor 2025-06-01T20:56:27.7565766Z  for requestor in workflow_requestors 2025-06-01T20:56:27.7566432Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.7567031Z  ] 2025-06-01T20:56:27.7567396Z  2025-06-01T20:56:27.7567746Z  enabled = False 2025-06-01T20:56:27.7568198Z  if opted_in_users: 2025-06-01T20:56:27.7568787Z  log.info( 2025-06-01T20:56:27.7569424Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:27.7570083Z  ) 2025-06-01T20:56:27.7571163Z  enabled = True 2025-06-01T20:56:27.7571909Z  2025-06-01T20:56:27.7572717Z  elif experiment_settings.rollout_perc: 2025-06-01T20:56:27.7574087Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:27.7575368Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:27.7576002Z  log.info( 2025-06-01T20:56:27.7576895Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:27.7577765Z  ) 2025-06-01T20:56:27.7578193Z  enabled = True 2025-06-01T20:56:27.7578646Z  2025-06-01T20:56:27.7578981Z  if enabled: 2025-06-01T20:56:27.7579431Z  label = experiment_name 2025-06-01T20:56:27.7579982Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:27.7580791Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:27.7581641Z  # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:27.7582695Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:27.7583372Z  if is_canary: 2025-06-01T20:56:27.7583892Z  label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:27.7584427Z  fleet_prefix = label 2025-06-01T20:56:27.7584910Z  else: 2025-06-01T20:56:27.7585368Z  prefixes.append(label) 2025-06-01T20:56:27.7585856Z  2025-06-01T20:56:27.7586207Z  if len(prefixes) > 1: 2025-06-01T20:56:27.7586671Z  log.error( 2025-06-01T20:56:27.7587692Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-06-01T20:56:27.7588744Z  ) 2025-06-01T20:56:27.7589135Z  prefixes = prefixes[:1] 2025-06-01T20:56:27.7589606Z  2025-06-01T20:56:27.7589960Z  # Fleet always comes first 2025-06-01T20:56:27.7590440Z  if fleet_prefix: 2025-06-01T20:56:27.7591111Z  prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:27.7591604Z  2025-06-01T20:56:27.7592054Z  return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:27.7592870Z  2025-06-01T20:56:27.7593206Z  2025-06-01T20:56:27.7593823Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:27.7594573Z  """ 2025-06-01T20:56:27.7595165Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:27.7595842Z  2025-06-01T20:56:27.7596409Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:27.7597085Z  """ 2025-06-01T20:56:27.7597492Z  gh = get_gh_client(github_token) 2025-06-01T20:56:27.7598040Z  issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:27.7598687Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:27.7599272Z  2025-06-01T20:56:27.7599595Z  2025-06-01T20:56:27.7600179Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:27.7601071Z  for _ in range(num_retries): 2025-06-01T20:56:27.7601573Z  try: 2025-06-01T20:56:27.7602017Z  req = Request(url=url, headers=headers) 2025-06-01T20:56:27.7602867Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:27.7603511Z  return json.loads(content) 2025-06-01T20:56:27.7604029Z  except Exception as e: 2025-06-01T20:56:27.7604593Z  log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:27.7605125Z  2025-06-01T20:56:27.7605691Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:27.7606375Z  return {} 2025-06-01T20:56:27.7606767Z  2025-06-01T20:56:27.7607087Z  2025-06-01T20:56:27.7607409Z @cache 2025-06-01T20:56:27.7608049Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:27.7608781Z  """ 2025-06-01T20:56:27.7609183Z  Dynamically get PR information 2025-06-01T20:56:27.7609663Z  """ 2025-06-01T20:56:27.7610179Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:27.7610788Z  headers = { 2025-06-01T20:56:27.7611273Z  "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:27.7611875Z  "Authorization": f"token {github_token}", 2025-06-01T20:56:27.7612509Z  } 2025-06-01T20:56:27.7612951Z  json_response: dict[str, Any] = download_json( 2025-06-01T20:56:27.7613552Z  url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:27.7614109Z  headers=headers, 2025-06-01T20:56:27.7614545Z  ) 2025-06-01T20:56:27.7614899Z  2025-06-01T20:56:27.7615258Z  if not json_response: 2025-06-01T20:56:27.7615851Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:27.7616461Z  return {} 2025-06-01T20:56:27.7616861Z  2025-06-01T20:56:27.7617214Z  return json_response 2025-06-01T20:56:27.7617645Z  2025-06-01T20:56:27.7617970Z  2025-06-01T20:56:27.7618541Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:27.7619243Z  """ 2025-06-01T20:56:27.7619774Z  Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:27.7620397Z  """ 2025-06-01T20:56:27.7620890Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:27.7621479Z  return { 2025-06-01T20:56:27.7622445Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:27.7623151Z  } 2025-06-01T20:56:27.7623502Z  2025-06-01T20:56:27.7623826Z  2025-06-01T20:56:27.7624183Z def main() -> None: 2025-06-01T20:56:27.7624632Z  args = parse_args() 2025-06-01T20:56:27.7625153Z  2025-06-01T20:56:27.7625575Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:27.7626088Z  2025-06-01T20:56:27.7626456Z  # Check if the PR is opt-out 2025-06-01T20:56:27.7626941Z  if args.pr_number: 2025-06-01T20:56:27.7627621Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:27.7628365Z  if OPT_OUT_LABEL in labels: 2025-06-01T20:56:27.7628858Z  log.info( 2025-06-01T20:56:27.7629571Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:27.7630303Z  ) 2025-06-01T20:56:27.7630876Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.7631525Z  sys.exit() 2025-06-01T20:56:27.7632074Z  2025-06-01T20:56:27.7632641Z  try: 2025-06-01T20:56:27.7633087Z  rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:27.7633793Z  args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:27.7634403Z  ) 2025-06-01T20:56:27.7634773Z  2025-06-01T20:56:27.7635159Z  username = get_potential_pr_author( 2025-06-01T20:56:27.7635692Z  args.github_token, 2025-06-01T20:56:27.7636169Z  args.github_repo, 2025-06-01T20:56:27.7636656Z  args.github_actor, 2025-06-01T20:56:27.7637153Z  args.github_ref_type, 2025-06-01T20:56:27.7637653Z  args.github_branch, 2025-06-01T20:56:27.7638120Z  ) 2025-06-01T20:56:27.7638477Z  2025-06-01T20:56:27.7638952Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:27.7639534Z  2025-06-01T20:56:27.7639941Z  runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:27.7640492Z  rollout_state, 2025-06-01T20:56:27.7641001Z  (args.github_issue_owner, username), 2025-06-01T20:56:27.7641546Z  args.github_branch, 2025-06-01T20:56:27.7642050Z  args.eligible_experiments, 2025-06-01T20:56:27.7642707Z  args.opt_out_experiments, 2025-06-01T20:56:27.7643204Z  is_canary, 2025-06-01T20:56:27.7643633Z  ) 2025-06-01T20:56:27.7643993Z  2025-06-01T20:56:27.7644349Z  except Exception as e: 2025-06-01T20:56:27.7644819Z  log.error( 2025-06-01T20:56:27.7645499Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:27.7646221Z  ) 2025-06-01T20:56:27.7646582Z  2025-06-01T20:56:27.7647100Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.7647725Z  2025-06-01T20:56:27.7648050Z  2025-06-01T20:56:27.7648397Z if __name__ == "__main__": 2025-06-01T20:56:27.7648850Z  main() 2025-06-01T20:56:27.7649215Z  2025-06-01T20:56:27.7649536Z EOF 2025-06-01T20:56:27.7649871Z  2025-06-01T20:56:27.7650227Z cat runner_determinator.py 2025-06-01T20:56:27.8088687Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:27.8089611Z env: 2025-06-01T20:56:27.8090281Z GITHUB_TOKEN: *** 2025-06-01T20:56:27.8090675Z ISSUE_NUMBER: 5132 2025-06-01T20:56:27.8091334Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:27.8091807Z ISSUE_OWNER: 2025-06-01T20:56:27.8092377Z CHECK_EXPERIMENTS: 2025-06-01T20:56:27.8092939Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:27.8093346Z PR_NUMBER: 2025-06-01T20:56:27.8093698Z ##[endgroup] 2025-06-01T20:56:27.8371961Z # flake8: noqa: G004 2025-06-01T20:56:27.8372537Z 2025-06-01T20:56:27.8372967Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:27.8373896Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:27.8374653Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:27.8375065Z 2025-06-01T20:56:27.8375222Z """ 2025-06-01T20:56:27.8375761Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:27.8376590Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:27.8377431Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:27.8378198Z of which runners should be used to run which job. 2025-06-01T20:56:27.8378579Z 2025-06-01T20:56:27.8378955Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:27.8380026Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:27.8380865Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:27.8381513Z list, defined. 2025-06-01T20:56:27.8381723Z 2025-06-01T20:56:27.8382069Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:27.8383193Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:27.8383969Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:27.8384384Z 2025-06-01T20:56:27.8384741Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:27.8385793Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:27.8386601Z experiments which the user could be opted in to. 2025-06-01T20:56:27.8386974Z 2025-06-01T20:56:27.8387167Z The user list has the following rules: 2025-06-01T20:56:27.8387493Z 2025-06-01T20:56:27.8387792Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:27.8388600Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:27.8389304Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:27.8389676Z 2025-06-01T20:56:27.8389833Z Example config: 2025-06-01T20:56:27.8390244Z # A list of experiments that can be opted into. 2025-06-01T20:56:27.8390869Z # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:27.8391444Z # Expected syntax is: 2025-06-01T20:56:27.8392042Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:27.8393499Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:27.8394094Z 2025-06-01T20:56:27.8394257Z experiments: 2025-06-01T20:56:27.8394624Z lf: 2025-06-01T20:56:27.8394970Z rollout_percent: 25 2025-06-01T20:56:27.8395409Z all_branches: false 2025-06-01T20:56:27.8395851Z default: true 2025-06-01T20:56:27.8396247Z --- 2025-06-01T20:56:27.8396433Z 2025-06-01T20:56:27.8396588Z # Opt-ins: 2025-06-01T20:56:27.8397133Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:27.8397944Z # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:27.8398672Z # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:27.8399287Z # Experiments should be from the above list. 2025-06-01T20:56:27.8399709Z 2025-06-01T20:56:27.8399977Z @User1,-lf,split_build 2025-06-01T20:56:27.8400402Z @User2,lf 2025-06-01T20:56:27.8400754Z @User3,split_build 2025-06-01T20:56:27.8401342Z """ 2025-06-01T20:56:27.8401520Z 2025-06-01T20:56:27.8401687Z import json 2025-06-01T20:56:27.8402038Z import logging 2025-06-01T20:56:27.8402603Z import os 2025-06-01T20:56:27.8402938Z import random 2025-06-01T20:56:27.8403288Z import re 2025-06-01T20:56:27.8403630Z import sys 2025-06-01T20:56:27.8404019Z from argparse import ArgumentParser 2025-06-01T20:56:27.8404510Z from collections.abc import Iterable 2025-06-01T20:56:27.8404999Z from functools import cache 2025-06-01T20:56:27.8405437Z from logging import LogRecord 2025-06-01T20:56:27.8405895Z from typing import Any, NamedTuple 2025-06-01T20:56:27.8406387Z from urllib.request import Request, urlopen 2025-06-01T20:56:27.8406737Z 2025-06-01T20:56:27.8406892Z import yaml 2025-06-01T20:56:27.8407252Z from github import Auth, Github 2025-06-01T20:56:27.8407702Z from github.Issue import Issue 2025-06-01T20:56:27.8407979Z 2025-06-01T20:56:27.8407986Z 2025-06-01T20:56:27.8408197Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:27.8408838Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:27.8409645Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:27.8410160Z 2025-06-01T20:56:27.8410377Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:27.8411060Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:27.8411555Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:27.8412061Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:27.8412642Z 2025-06-01T20:56:27.8412839Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:27.8413151Z 2025-06-01T20:56:27.8413330Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:27.8413785Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:27.8414041Z 2025-06-01T20:56:27.8414047Z 2025-06-01T20:56:27.8414234Z class Experiment(NamedTuple): 2025-06-01T20:56:27.8414678Z rollout_perc: float = ( 2025-06-01T20:56:27.8415279Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:27.8415914Z ) 2025-06-01T20:56:27.8416262Z all_branches: bool = ( 2025-06-01T20:56:27.8416851Z False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:27.8417483Z ) 2025-06-01T20:56:27.8417810Z default: bool = ( 2025-06-01T20:56:27.8418350Z True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:27.8418956Z ) 2025-06-01T20:56:27.8419132Z 2025-06-01T20:56:27.8419304Z # Add more fields as needed 2025-06-01T20:56:27.8419588Z 2025-06-01T20:56:27.8419600Z 2025-06-01T20:56:27.8419778Z class Settings(NamedTuple): 2025-06-01T20:56:27.8420180Z """ 2025-06-01T20:56:27.8420601Z Settings for the experiments that can be opted into. 2025-06-01T20:56:27.8421127Z """ 2025-06-01T20:56:27.8421312Z 2025-06-01T20:56:27.8421505Z experiments: dict[str, Experiment] = {} 2025-06-01T20:56:27.8421842Z 2025-06-01T20:56:27.8421848Z 2025-06-01T20:56:27.8422047Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:27.8422933Z """Color codes the log messages based on the log level""" 2025-06-01T20:56:27.8423344Z 2025-06-01T20:56:27.8423497Z COLORS = { 2025-06-01T20:56:27.8423861Z "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:27.8424338Z "ERROR": "\033[31m", # Red 2025-06-01T20:56:27.8424800Z "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:27.8425262Z "INFO": "\033[0m", # Reset 2025-06-01T20:56:27.8425712Z "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:27.8426136Z } 2025-06-01T20:56:27.8426310Z 2025-06-01T20:56:27.8426516Z def format(self, record: LogRecord) -> str: 2025-06-01T20:56:27.8427201Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:27.8427940Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:27.8428479Z return super().format(record) 2025-06-01T20:56:27.8428797Z 2025-06-01T20:56:27.8428804Z 2025-06-01T20:56:27.8428986Z handler = logging.StreamHandler() 2025-06-01T20:56:27.8429809Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:27.8430321Z 2025-06-01T20:56:27.8430552Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:27.8431101Z log.addHandler(handler) 2025-06-01T20:56:27.8431509Z log.setLevel(logging.INFO) 2025-06-01T20:56:27.8431776Z 2025-06-01T20:56:27.8431782Z 2025-06-01T20:56:27.8432012Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:27.8432714Z """ 2025-06-01T20:56:27.8433187Z Defines outputs of the github action that invokes this script 2025-06-01T20:56:27.8433770Z """ 2025-06-01T20:56:27.8434100Z if not GITHUB_OUTPUT: 2025-06-01T20:56:27.8435090Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:27.8436116Z log.warning( 2025-06-01T20:56:27.8436907Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-06-01T20:56:27.8437768Z ) 2025-06-01T20:56:27.8448095Z print(f"::set-output name={key}::{value}") 2025-06-01T20:56:27.8448685Z return 2025-06-01T20:56:27.8448893Z 2025-06-01T20:56:27.8449287Z with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:27.8449868Z log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:27.8450399Z f.write(f"{key}={value}\n") 2025-06-01T20:56:27.8450700Z 2025-06-01T20:56:27.8450707Z 2025-06-01T20:56:27.8450991Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:27.8451580Z return frozenset( 2025-06-01T20:56:27.8452443Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:27.8453111Z ) 2025-06-01T20:56:27.8453296Z 2025-06-01T20:56:27.8453304Z 2025-06-01T20:56:27.8453478Z def parse_args() -> Any: 2025-06-01T20:56:27.8453994Z parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:27.8454818Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:27.8455543Z parser.add_argument( 2025-06-01T20:56:27.8455955Z "--github-issue-repo", 2025-06-01T20:56:27.8456380Z type=str, 2025-06-01T20:56:27.8456751Z required=False, 2025-06-01T20:56:27.8457166Z default="pytorch/test-infra", 2025-06-01T20:56:27.8457659Z help="GitHub repo to get the issue", 2025-06-01T20:56:27.8458124Z ) 2025-06-01T20:56:27.8458465Z parser.add_argument( 2025-06-01T20:56:27.8458879Z "--github-repo", 2025-06-01T20:56:27.8459275Z type=str, 2025-06-01T20:56:27.8459636Z required=True, 2025-06-01T20:56:27.8460063Z help="GitHub repo where CI is running", 2025-06-01T20:56:27.8460539Z ) 2025-06-01T20:56:27.8460876Z parser.add_argument( 2025-06-01T20:56:27.8461428Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:27.8462036Z ) 2025-06-01T20:56:27.8462569Z parser.add_argument( 2025-06-01T20:56:27.8463147Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:27.8463768Z ) 2025-06-01T20:56:27.8464095Z parser.add_argument( 2025-06-01T20:56:27.8464743Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:27.8465379Z ) 2025-06-01T20:56:27.8465717Z parser.add_argument( 2025-06-01T20:56:27.8466317Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:27.8466979Z ) 2025-06-01T20:56:27.8467306Z parser.add_argument( 2025-06-01T20:56:27.8467714Z "--github-ref-type", 2025-06-01T20:56:27.8468126Z type=str, 2025-06-01T20:56:27.8468484Z required=True, 2025-06-01T20:56:27.8468925Z help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:27.8469425Z ) 2025-06-01T20:56:27.8469756Z parser.add_argument( 2025-06-01T20:56:27.8470172Z "--eligible-experiments", 2025-06-01T20:56:27.8470799Z type=_str_comma_separated_to_set, 2025-06-01T20:56:27.8471275Z required=False, 2025-06-01T20:56:27.8471671Z default="", 2025-06-01T20:56:27.8472722Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:27.8473606Z ) 2025-06-01T20:56:27.8473941Z parser.add_argument( 2025-06-01T20:56:27.8474357Z "--opt-out-experiments", 2025-06-01T20:56:27.8474832Z type=_str_comma_separated_to_set, 2025-06-01T20:56:27.8475315Z required=False, 2025-06-01T20:56:27.8475705Z default="", 2025-06-01T20:56:27.8476056Z help=( 2025-06-01T20:56:27.8476680Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:27.8477736Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:27.8478510Z ), 2025-06-01T20:56:27.8478841Z ) 2025-06-01T20:56:27.8479173Z parser.add_argument( 2025-06-01T20:56:27.8479573Z "--pr-number", 2025-06-01T20:56:27.8479942Z type=str, 2025-06-01T20:56:27.8480306Z required=False, 2025-06-01T20:56:27.8480684Z default="", 2025-06-01T20:56:27.8481280Z help="the optional PR number where this is run", 2025-06-01T20:56:27.8481794Z ) 2025-06-01T20:56:27.8481978Z 2025-06-01T20:56:27.8482345Z return parser.parse_args() 2025-06-01T20:56:27.8482670Z 2025-06-01T20:56:27.8482676Z 2025-06-01T20:56:27.8483063Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.8483757Z auth = Auth.Token(github_token) 2025-06-01T20:56:27.8484218Z return Github(auth=auth) 2025-06-01T20:56:27.8484481Z 2025-06-01T20:56:27.8484488Z 2025-06-01T20:56:27.8484905Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.8485646Z repo = gh.get_repo(repo) 2025-06-01T20:56:27.8486124Z return repo.get_issue(number=issue_num) 2025-06-01T20:56:27.8486460Z 2025-06-01T20:56:27.8486466Z 2025-06-01T20:56:27.8486641Z def get_potential_pr_author( 2025-06-01T20:56:27.8487251Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:27.8487860Z ) -> str: 2025-06-01T20:56:27.8488336Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:27.8489072Z # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:27.8489751Z # embedded in the tag name: ciflow// 2025-06-01T20:56:27.8490132Z 2025-06-01T20:56:27.8490305Z gh = get_gh_client(github_token) 2025-06-01T20:56:27.8490616Z 2025-06-01T20:56:27.8490864Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:27.8491443Z split_tag = ref_name.split("/") 2025-06-01T20:56:27.8492476Z if ( 2025-06-01T20:56:27.8492916Z len(split_tag) == 3 2025-06-01T20:56:27.8493368Z and split_tag[0] == "ciflow" 2025-06-01T20:56:27.8493857Z and split_tag[2].isnumeric() 2025-06-01T20:56:27.8494306Z ): 2025-06-01T20:56:27.8494658Z pr_number = split_tag[2] 2025-06-01T20:56:27.8495094Z try: 2025-06-01T20:56:27.8495496Z repository = gh.get_repo(repo) 2025-06-01T20:56:27.8496061Z pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:27.8496610Z except Exception as e: 2025-06-01T20:56:27.8497091Z raise Exception( # noqa: TRY002 2025-06-01T20:56:27.8497701Z f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:27.8498296Z ) from e 2025-06-01T20:56:27.8498781Z return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:27.8499428Z # In all other cases, return the original input username 2025-06-01T20:56:27.8499974Z return username 2025-06-01T20:56:27.8500364Z 2025-06-01T20:56:27.8500371Z 2025-06-01T20:56:27.8500587Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:27.8501081Z """ 2025-06-01T20:56:27.8501662Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:27.8502629Z """ 2025-06-01T20:56:27.8503161Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:27.8503665Z 2025-06-01T20:56:27.8503673Z 2025-06-01T20:56:27.8503858Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:27.8504325Z try: 2025-06-01T20:56:27.8504679Z data = yaml.safe_load(yaml_text) 2025-06-01T20:56:27.8505154Z return data 2025-06-01T20:56:27.8505529Z except yaml.YAMLError: 2025-06-01T20:56:27.8505971Z log.exception("Error loading YAML") 2025-06-01T20:56:27.8506438Z raise 2025-06-01T20:56:27.8506635Z 2025-06-01T20:56:27.8506642Z 2025-06-01T20:56:27.8507041Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:27.8507720Z """ 2025-06-01T20:56:27.8508294Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:27.8508850Z 2025-06-01T20:56:27.8509319Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.8510018Z and the text below is the list of opted in users. 2025-06-01T20:56:27.8510397Z 2025-06-01T20:56:27.8510751Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:27.8511385Z """ 2025-06-01T20:56:27.8511795Z rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:27.8512483Z if len(rollout_state_parts) >= 2: 2025-06-01T20:56:27.8513038Z return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:27.8513578Z else: 2025-06-01T20:56:27.8513919Z return "", rollout_state 2025-06-01T20:56:27.8514203Z 2025-06-01T20:56:27.8514211Z 2025-06-01T20:56:27.8514414Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:27.8514876Z """ 2025-06-01T20:56:27.8515348Z Dictionary of users with a list of features they have opted into 2025-06-01T20:56:27.8515941Z """ 2025-06-01T20:56:27.8516120Z 2025-06-01T20:56:27.8516125Z 2025-06-01T20:56:27.8516441Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:27.8517042Z """ 2025-06-01T20:56:27.8517688Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-06-01T20:56:27.8518322Z 2025-06-01T20:56:27.8518900Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:27.8519817Z - Example line: "@User1,lf,split_build" 2025-06-01T20:56:27.8520450Z - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:27.8520900Z 2025-06-01T20:56:27.8520906Z 2025-06-01T20:56:27.8521058Z """ 2025-06-01T20:56:27.8521396Z optins = UserOptins() 2025-06-01T20:56:27.8521845Z for user in user_optin_text.split("\n"): 2025-06-01T20:56:27.8522463Z user = user.strip("\r\n\t -") 2025-06-01T20:56:27.8522971Z if not user or not user.startswith("@"): 2025-06-01T20:56:27.8523484Z # Not a valid user. Skip 2025-06-01T20:56:27.8523934Z continue 2025-06-01T20:56:27.8524155Z 2025-06-01T20:56:27.8524301Z if user: 2025-06-01T20:56:27.8524706Z usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:27.8525343Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:27.8525795Z 2025-06-01T20:56:27.8525954Z return optins 2025-06-01T20:56:27.8526165Z 2025-06-01T20:56:27.8526178Z 2025-06-01T20:56:27.8526442Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:27.8526991Z """ 2025-06-01T20:56:27.8527354Z Check if the experiment name is valid. 2025-06-01T20:56:27.8527828Z A valid name: 2025-06-01T20:56:27.8528554Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:27.8580041Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:27.8581541Z - Cannot contain spaces 2025-06-01T20:56:27.8582455Z """ 2025-06-01T20:56:27.8582730Z 2025-06-01T20:56:27.8582992Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:27.8583682Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:27.8584101Z 2025-06-01T20:56:27.8584251Z if valid: 2025-06-01T20:56:27.8584602Z return True 2025-06-01T20:56:27.8584818Z 2025-06-01T20:56:27.8584968Z log.error( 2025-06-01T20:56:27.8586338Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-06-01T20:56:27.8587839Z ) 2025-06-01T20:56:27.8588155Z return False 2025-06-01T20:56:27.8588369Z 2025-06-01T20:56:27.8588376Z 2025-06-01T20:56:27.8588660Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:27.8589232Z """ 2025-06-01T20:56:27.8590006Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:27.8590691Z """ 2025-06-01T20:56:27.8591010Z try: 2025-06-01T20:56:27.8591354Z if settings_text: 2025-06-01T20:56:27.8592019Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:27.8593049Z # for easy reading 2025-06-01T20:56:27.8593785Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:27.8594599Z # the backtick character in shell commands. 2025-06-01T20:56:27.8595157Z backtick = chr(96) # backtick character 2025-06-01T20:56:27.8595772Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:27.8596384Z settings = load_yaml(settings_text) 2025-06-01T20:56:27.8596718Z 2025-06-01T20:56:27.8597097Z # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:27.8597792Z experiments = {} 2025-06-01T20:56:27.8598053Z 2025-06-01T20:56:27.8598391Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:27.8599081Z if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:27.8600108Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-06-01T20:56:27.8601060Z continue 2025-06-01T20:56:27.8601327Z 2025-06-01T20:56:27.8601491Z valid_settings = {} 2025-06-01T20:56:27.8601960Z for setting in exp_settings: 2025-06-01T20:56:27.8602659Z if setting not in Experiment._fields: 2025-06-01T20:56:27.8603172Z log.warning( 2025-06-01T20:56:27.8603808Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:27.8604466Z ) 2025-06-01T20:56:27.8604847Z else: 2025-06-01T20:56:27.8605317Z valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:27.8605706Z 2025-06-01T20:56:27.8605961Z experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:27.8606538Z return Settings(experiments) 2025-06-01T20:56:27.8606854Z 2025-06-01T20:56:27.8607024Z except Exception: 2025-06-01T20:56:27.8607450Z log.exception("Failed to parse settings") 2025-06-01T20:56:27.8607804Z 2025-06-01T20:56:27.8607968Z return Settings() 2025-06-01T20:56:27.8608194Z 2025-06-01T20:56:27.8608200Z 2025-06-01T20:56:27.8608433Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:27.8609134Z """ 2025-06-01T20:56:27.8609520Z Parse settings, if any, from the rollout state. 2025-06-01T20:56:27.8609893Z 2025-06-01T20:56:27.8610222Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.8610921Z and the text below is the list of opted in users. 2025-06-01T20:56:27.8611292Z 2025-06-01T20:56:27.8611666Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:27.8612525Z """ 2025-06-01T20:56:27.8613029Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.8613714Z return parse_settings_from_text(settings_text) 2025-06-01T20:56:27.8614072Z 2025-06-01T20:56:27.8614079Z 2025-06-01T20:56:27.8614311Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:27.8614815Z """ 2025-06-01T20:56:27.8615165Z Parse users from the rollout state. 2025-06-01T20:56:27.8615489Z 2025-06-01T20:56:27.8615636Z """ 2025-06-01T20:56:27.8616112Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.8616775Z return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:27.8617138Z 2025-06-01T20:56:27.8617145Z 2025-06-01T20:56:27.8617655Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.8618357Z """ 2025-06-01T20:56:27.8618723Z Check if a user is opted into an experiment 2025-06-01T20:56:27.8619213Z """ 2025-06-01T20:56:27.8619618Z return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:27.8620002Z 2025-06-01T20:56:27.8620008Z 2025-06-01T20:56:27.8620387Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.8621064Z """ 2025-06-01T20:56:27.8621473Z Check if a user explicitly opted out of an experiment 2025-06-01T20:56:27.8622000Z """ 2025-06-01T20:56:27.8622702Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:27.8623338Z experiment_optout = "-" + experiment_name 2025-06-01T20:56:27.8623913Z if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:27.8624459Z return False 2025-06-01T20:56:27.8624679Z 2025-06-01T20:56:27.8624943Z if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:27.8625493Z log.warning( 2025-06-01T20:56:27.8626217Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:27.8627024Z ) 2025-06-01T20:56:27.8627204Z 2025-06-01T20:56:27.8627365Z return True 2025-06-01T20:56:27.8627573Z 2025-06-01T20:56:27.8627578Z 2025-06-01T20:56:27.8627744Z def get_runner_prefix( 2025-06-01T20:56:27.8628136Z rollout_state: str, 2025-06-01T20:56:27.8628581Z workflow_requestors: Iterable[str], 2025-06-01T20:56:27.8629048Z branch: str, 2025-06-01T20:56:27.8629483Z eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.8630097Z opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.8630632Z is_canary: bool = False, 2025-06-01T20:56:27.8631039Z ) -> str: 2025-06-01T20:56:27.8631423Z settings = parse_settings(rollout_state) 2025-06-01T20:56:27.8631947Z user_optins = parse_users(rollout_state) 2025-06-01T20:56:27.8632789Z 2025-06-01T20:56:27.8632963Z fleet_prefix = "" 2025-06-01T20:56:27.8633350Z prefixes = [] 2025-06-01T20:56:27.8633918Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:27.8634768Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:27.8635417Z log.info( 2025-06-01T20:56:27.8636038Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:27.8636725Z ) 2025-06-01T20:56:27.8637071Z continue 2025-06-01T20:56:27.8637306Z 2025-06-01T20:56:27.8637651Z if opt_out_experiments: 2025-06-01T20:56:27.8638143Z if experiment_name in opt_out_experiments: 2025-06-01T20:56:27.8638711Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:27.8639245Z log.info( 2025-06-01T20:56:27.8640100Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:27.8640990Z ) 2025-06-01T20:56:27.8641348Z continue 2025-06-01T20:56:27.8641583Z 2025-06-01T20:56:27.8641753Z if eligible_experiments: 2025-06-01T20:56:27.8642602Z if experiment_name not in eligible_experiments: 2025-06-01T20:56:27.8643204Z exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:27.8643714Z log.info( 2025-06-01T20:56:27.8644448Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:27.8645221Z ) 2025-06-01T20:56:27.8645578Z continue 2025-06-01T20:56:27.8646000Z elif not experiment_settings.default: 2025-06-01T20:56:27.8646490Z log.info( 2025-06-01T20:56:27.8647217Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:27.8647911Z ) 2025-06-01T20:56:27.8648269Z continue 2025-06-01T20:56:27.8648496Z 2025-06-01T20:56:27.8648751Z # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:27.8649307Z opted_out_users = [ 2025-06-01T20:56:27.8649707Z requestor 2025-06-01T20:56:27.8650119Z for requestor in workflow_requestors 2025-06-01T20:56:27.8650724Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.8651294Z ] 2025-06-01T20:56:27.8651478Z 2025-06-01T20:56:27.8651637Z if opted_out_users: 2025-06-01T20:56:27.8652050Z log.info( 2025-06-01T20:56:27.8653103Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:27.8653785Z ) 2025-06-01T20:56:27.8654126Z continue 2025-06-01T20:56:27.8654355Z 2025-06-01T20:56:27.8654620Z # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:27.8655181Z opted_in_users = [ 2025-06-01T20:56:27.8655586Z requestor 2025-06-01T20:56:27.8656002Z for requestor in workflow_requestors 2025-06-01T20:56:27.8656604Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.8657341Z ] 2025-06-01T20:56:27.8657538Z 2025-06-01T20:56:27.8657698Z enabled = False 2025-06-01T20:56:27.8658098Z if opted_in_users: 2025-06-01T20:56:27.8658500Z log.info( 2025-06-01T20:56:27.8659056Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:27.8659690Z ) 2025-06-01T20:56:27.8660036Z enabled = True 2025-06-01T20:56:27.8660290Z 2025-06-01T20:56:27.8660499Z elif experiment_settings.rollout_perc: 2025-06-01T20:56:27.8661275Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:27.8662380Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:27.8663007Z log.info( 2025-06-01T20:56:27.8663802Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:27.8664649Z ) 2025-06-01T20:56:27.8665009Z enabled = True 2025-06-01T20:56:27.8665277Z 2025-06-01T20:56:27.8665431Z if enabled: 2025-06-01T20:56:27.8665798Z label = experiment_name 2025-06-01T20:56:27.8666304Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:27.8667057Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:27.8668056Z # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:27.8668760Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:27.8669361Z if is_canary: 2025-06-01T20:56:27.8669803Z label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:27.8670289Z fleet_prefix = label 2025-06-01T20:56:27.8670729Z else: 2025-06-01T20:56:27.8671104Z prefixes.append(label) 2025-06-01T20:56:27.8671417Z 2025-06-01T20:56:27.8671583Z if len(prefixes) > 1: 2025-06-01T20:56:27.8671978Z log.error( 2025-06-01T20:56:27.8673160Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-06-01T20:56:27.8674190Z ) 2025-06-01T20:56:27.8674541Z prefixes = prefixes[:1] 2025-06-01T20:56:27.8674823Z 2025-06-01T20:56:27.8674995Z # Fleet always comes first 2025-06-01T20:56:27.8675424Z if fleet_prefix: 2025-06-01T20:56:27.8675835Z prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:27.8676155Z 2025-06-01T20:56:27.8676515Z return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:27.8676907Z 2025-06-01T20:56:27.8676913Z 2025-06-01T20:56:27.8677323Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:27.8678031Z """ 2025-06-01T20:56:27.8678558Z Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:27.8679071Z 2025-06-01T20:56:27.8679420Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:27.8680050Z """ 2025-06-01T20:56:27.8680398Z gh = get_gh_client(github_token) 2025-06-01T20:56:27.8680885Z issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:27.8681464Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:27.8681865Z 2025-06-01T20:56:27.8681871Z 2025-06-01T20:56:27.8682380Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:27.8683076Z for _ in range(num_retries): 2025-06-01T20:56:27.8683511Z try: 2025-06-01T20:56:27.8683888Z req = Request(url=url, headers=headers) 2025-06-01T20:56:27.8684487Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:27.8685062Z return json.loads(content) 2025-06-01T20:56:27.8685526Z except Exception as e: 2025-06-01T20:56:27.8686010Z log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:27.8686370Z 2025-06-01T20:56:27.8686720Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:27.8687367Z return {} 2025-06-01T20:56:27.8687561Z 2025-06-01T20:56:27.8687567Z 2025-06-01T20:56:27.8687711Z @cache 2025-06-01T20:56:27.8688281Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:27.8688969Z """ 2025-06-01T20:56:27.8689315Z Dynamically get PR information 2025-06-01T20:56:27.8689761Z """ 2025-06-01T20:56:27.8690202Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:27.8690767Z headers = { 2025-06-01T20:56:27.8691172Z "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:27.8691720Z "Authorization": f"token {github_token}", 2025-06-01T20:56:27.8692337Z } 2025-06-01T20:56:27.8692728Z json_response: dict[str, Any] = download_json( 2025-06-01T20:56:27.8693273Z url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:27.8693765Z headers=headers, 2025-06-01T20:56:27.8694160Z ) 2025-06-01T20:56:27.8694336Z 2025-06-01T20:56:27.8694504Z if not json_response: 2025-06-01T20:56:27.8695013Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:27.8695704Z return {} 2025-06-01T20:56:27.8695920Z 2025-06-01T20:56:27.8696085Z return json_response 2025-06-01T20:56:27.8696329Z 2025-06-01T20:56:27.8696335Z 2025-06-01T20:56:27.8696708Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:27.8697382Z """ 2025-06-01T20:56:27.8697865Z Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:27.8698448Z """ 2025-06-01T20:56:27.8698888Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:27.8699437Z return { 2025-06-01T20:56:27.8699960Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:27.8700590Z } 2025-06-01T20:56:27.8700771Z 2025-06-01T20:56:27.8700777Z 2025-06-01T20:56:27.8700933Z def main() -> None: 2025-06-01T20:56:27.8701307Z args = parse_args() 2025-06-01T20:56:27.8701541Z 2025-06-01T20:56:27.8701741Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:27.8702091Z 2025-06-01T20:56:27.8702487Z # Check if the PR is opt-out 2025-06-01T20:56:27.8702932Z if args.pr_number: 2025-06-01T20:56:27.8703521Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:27.8704347Z if OPT_OUT_LABEL in labels: 2025-06-01T20:56:27.8704823Z log.info( 2025-06-01T20:56:27.8705444Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:27.8706137Z ) 2025-06-01T20:56:27.8706634Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.8707238Z sys.exit() 2025-06-01T20:56:27.8707473Z 2025-06-01T20:56:27.8707615Z try: 2025-06-01T20:56:27.8708002Z rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:27.8708644Z args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:27.8709212Z ) 2025-06-01T20:56:27.8709394Z 2025-06-01T20:56:27.8709587Z username = get_potential_pr_author( 2025-06-01T20:56:27.8710077Z args.github_token, 2025-06-01T20:56:27.8710501Z args.github_repo, 2025-06-01T20:56:27.8710924Z args.github_actor, 2025-06-01T20:56:27.8711345Z args.github_ref_type, 2025-06-01T20:56:27.8711787Z args.github_branch, 2025-06-01T20:56:27.8712414Z ) 2025-06-01T20:56:27.8712617Z 2025-06-01T20:56:27.8712875Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:27.8713285Z 2025-06-01T20:56:27.8713489Z runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:27.8713981Z rollout_state, 2025-06-01T20:56:27.8714413Z (args.github_issue_owner, username), 2025-06-01T20:56:27.8714902Z args.github_branch, 2025-06-01T20:56:27.8715346Z args.eligible_experiments, 2025-06-01T20:56:27.8715822Z args.opt_out_experiments, 2025-06-01T20:56:27.8716279Z is_canary, 2025-06-01T20:56:27.8716648Z ) 2025-06-01T20:56:27.8716832Z 2025-06-01T20:56:27.8717003Z except Exception as e: 2025-06-01T20:56:27.8717411Z log.error( 2025-06-01T20:56:27.8718021Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:27.8718720Z ) 2025-06-01T20:56:27.8718898Z 2025-06-01T20:56:27.8719193Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.8719644Z 2025-06-01T20:56:27.8719651Z 2025-06-01T20:56:27.8719814Z if __name__ == "__main__": 2025-06-01T20:56:27.8720202Z main() 2025-06-01T20:56:27.8720394Z 2025-06-01T20:56:27.8811861Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:27.8812946Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:27.8863963Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:27.8864410Z env: 2025-06-01T20:56:27.8864974Z GITHUB_TOKEN: *** 2025-06-01T20:56:27.8865347Z ISSUE_NUMBER: 5132 2025-06-01T20:56:27.8865961Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:27.8866408Z ISSUE_OWNER: 2025-06-01T20:56:27.8866763Z CHECK_EXPERIMENTS: 2025-06-01T20:56:27.8867152Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:27.8867540Z PR_NUMBER: 2025-06-01T20:56:27.8867874Z ##[endgroup] 2025-06-01T20:56:28.4454294Z Defaulting to user installation because normal site-packages is not writeable 2025-06-01T20:56:29.1584443Z Collecting urllib3==1.26.18 2025-06-01T20:56:29.2387212Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-06-01T20:56:29.2629829Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.0 MB/s eta 0:00:00 2025-06-01T20:56:29.2942313Z Collecting PyGithub==2.3.0 2025-06-01T20:56:29.3049333Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-06-01T20:56:29.3576362Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-06-01T20:56:29.3680798Z 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:29.3733768Z 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:29.3751898Z 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:29.3766959Z 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:29.4097771Z Collecting Deprecated (from PyGithub==2.3.0) 2025-06-01T20:56:29.4199530Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-06-01T20:56:29.4438033Z 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:29.5770051Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:29.5878903Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-06-01T20:56:29.7004275Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-06-01T20:56:29.7107783Z 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:29.7367677Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:29.7484032Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-06-01T20:56:29.7791185Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-06-01T20:56:29.7986146Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 7.7 MB/s eta 0:00:00 2025-06-01T20:56:29.8100199Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-06-01T20:56:29.8452769Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 10.4 MB/s eta 0:00:00 2025-06-01T20:56:29.8552916Z 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:29.9194590Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 13.6 MB/s eta 0:00:00 2025-06-01T20:56:29.9295905Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-06-01T20:56:29.9425124Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-06-01T20:56:29.9735662Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 16.2 MB/s eta 0:00:00 2025-06-01T20:56:29.9839318Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (89 kB) 2025-06-01T20:56:29.9904669Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 17.1 MB/s eta 0:00:00 2025-06-01T20:56:30.0003336Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-06-01T20:56:30.0084994Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 17.1 MB/s eta 0:00:00 2025-06-01T20:56:30.3131506Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-06-01T20:56:30.8577409Z 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:30.9508220Z ##[group]Run curr_branch="main" 2025-06-01T20:56:30.9508602Z curr_branch="main" 2025-06-01T20:56:30.9508874Z curr_ref_type="branch" 2025-06-01T20:56:30.9509161Z echo "Current branch is '$curr_branch'" 2025-06-01T20:56:30.9509464Z  2025-06-01T20:56:30.9509698Z python3 runner_determinator.py \ 2025-06-01T20:56:30.9510016Z  --github-token "$GITHUB_TOKEN" \ 2025-06-01T20:56:30.9510321Z  --github-issue "$ISSUE_NUMBER" \ 2025-06-01T20:56:30.9510648Z  --github-branch "$curr_branch" \ 2025-06-01T20:56:30.9510980Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-06-01T20:56:30.9511314Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-06-01T20:56:30.9511652Z  --github-ref-type "$curr_ref_type" \ 2025-06-01T20:56:30.9511972Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-06-01T20:56:30.9512596Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-06-01T20:56:30.9513003Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-06-01T20:56:30.9513326Z  --pr-number "${PR_NUMBER}" 2025-06-01T20:56:30.9565625Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:30.9565907Z env: 2025-06-01T20:56:30.9566640Z GITHUB_TOKEN: *** 2025-06-01T20:56:30.9566876Z ISSUE_NUMBER: 5132 2025-06-01T20:56:30.9567113Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:30.9567379Z ISSUE_OWNER: 2025-06-01T20:56:30.9567599Z CHECK_EXPERIMENTS: 2025-06-01T20:56:30.9567832Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:30.9568049Z PR_NUMBER: 2025-06-01T20:56:30.9568248Z ##[endgroup] 2025-06-01T20:56:30.9643840Z Current branch is 'main' 2025-06-01T20:56:32.7779623Z INFO : Based on rollout percentage of 30%, enabling experiment lf. 2025-06-01T20:56:32.7780966Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-06-01T20:56:32.7782639Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-06-01T20:56:32.7783630Z INFO : Setting output: label-type='lf.' 2025-06-01T20:56:32.8133459Z Evaluate and set job outputs 2025-06-01T20:56:32.8140167Z Set output 'label-type' 2025-06-01T20:56:32.8141905Z Cleaning up orphan processes