2025-12-04T07:56:46.4179806Z Current runner version: '2.329.0' 2025-12-04T07:56:46.4202210Z ##[group]Runner Image Provisioner 2025-12-04T07:56:46.4202909Z Hosted Compute Agent 2025-12-04T07:56:46.4203474Z Version: 20251124.448 2025-12-04T07:56:46.4203995Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T07:56:46.4204788Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T07:56:46.4205308Z ##[endgroup] 2025-12-04T07:56:46.4205795Z ##[group]Operating System 2025-12-04T07:56:46.4206313Z Ubuntu 2025-12-04T07:56:46.4206733Z 24.04.3 2025-12-04T07:56:46.4207178Z LTS 2025-12-04T07:56:46.4207612Z ##[endgroup] 2025-12-04T07:56:46.4208071Z ##[group]Runner Image 2025-12-04T07:56:46.4208533Z Image: ubuntu-24.04 2025-12-04T07:56:46.4208986Z Version: 20251126.144.1 2025-12-04T07:56:46.4209836Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T07:56:46.4211273Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T07:56:46.4212319Z ##[endgroup] 2025-12-04T07:56:46.4213277Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T07:56:46.4215486Z Contents: read 2025-12-04T07:56:46.4215994Z Metadata: read 2025-12-04T07:56:46.4216432Z ##[endgroup] 2025-12-04T07:56:46.4218321Z Secret source: Actions 2025-12-04T07:56:46.4219303Z Prepare workflow directory 2025-12-04T07:56:46.4732554Z Prepare all required actions 2025-12-04T07:56:46.4784410Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T07:56:46.4788950Z ##[group] Inputs 2025-12-04T07:56:46.4789464Z check_experiments: 2025-12-04T07:56:46.4790040Z opt_out_experiments: 2025-12-04T07:56:46.4790511Z triggering_actor: pytorchmergebot 2025-12-04T07:56:46.4791025Z issue_owner: 2025-12-04T07:56:46.4791498Z curr_branch: main 2025-12-04T07:56:46.4791949Z curr_ref_type: branch 2025-12-04T07:56:46.4792493Z issue_number: 5132 2025-12-04T07:56:46.4792969Z ##[endgroup] 2025-12-04T07:56:46.4793586Z Complete job name: before-test / get-label-type / runner-determinator 2025-12-04T07:56:47.1402547Z ##[group]Run cat < runner_determinator.py 2025-12-04T07:56:47.1404880Z cat < runner_determinator.py 2025-12-04T07:56:47.1405536Z # flake8: noqa: G004 2025-12-04T07:56:47.1406087Z  2025-12-04T07:56:47.1406918Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T07:56:47.1407979Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T07:56:47.1408935Z # python .github/scripts/update_runner_determinator.py 2025-12-04T07:56:47.1409658Z  2025-12-04T07:56:47.1410121Z """ 2025-12-04T07:56:47.1410841Z This runner determinator is used to determine which set of runners to run a 2025-12-04T07:56:47.1411877Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T07:56:47.1413075Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T07:56:47.1414046Z of which runners should be used to run which job. 2025-12-04T07:56:47.1414895Z  2025-12-04T07:56:47.1415584Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T07:56:47.1416670Z separated by a line containing "---". If the line is not present, the 2025-12-04T07:56:47.1417723Z settings are considered to be empty with only the second part, the user 2025-12-04T07:56:47.1418549Z list, defined. 2025-12-04T07:56:47.1419087Z  2025-12-04T07:56:47.1419771Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T07:56:47.1420852Z used to define any settings that are needed to determine which runners to use. 2025-12-04T07:56:47.1421863Z It's fields are defined by the RolloutSettings class below. 2025-12-04T07:56:47.1422583Z  2025-12-04T07:56:47.1423505Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T07:56:47.1424500Z The user list is also a comma separated list of additional features or 2025-12-04T07:56:47.1425525Z experiments which the user could be opted in to. 2025-12-04T07:56:47.1426191Z  2025-12-04T07:56:47.1426702Z The user list has the following rules: 2025-12-04T07:56:47.1427375Z  2025-12-04T07:56:47.1428039Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T07:56:47.1429043Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T07:56:47.1429969Z - A "#" prefix opts the user out of all experiments 2025-12-04T07:56:47.1430658Z  2025-12-04T07:56:47.1431104Z Example config: 2025-12-04T07:56:47.1432069Z  # A list of experiments that can be opted into. 2025-12-04T07:56:47.1432904Z  # This defines the behavior they'll induce when opted into. 2025-12-04T07:56:47.1433648Z  # Expected syntax is: 2025-12-04T07:56:47.1434489Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T07:56:47.1435670Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T07:56:47.1436593Z  2025-12-04T07:56:47.1437089Z  experiments: 2025-12-04T07:56:47.1437600Z  lf: 2025-12-04T07:56:47.1438132Z  rollout_percent: 25 2025-12-04T07:56:47.1438723Z  all_branches: false 2025-12-04T07:56:47.1439338Z  default: true 2025-12-04T07:56:47.1439893Z  --- 2025-12-04T07:56:47.1440405Z  2025-12-04T07:56:47.1440880Z  # Opt-ins: 2025-12-04T07:56:47.1441617Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T07:56:47.1442804Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T07:56:47.1443724Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T07:56:47.1444876Z  # Experiments should be from the above list. 2025-12-04T07:56:47.1445582Z  2025-12-04T07:56:47.1446057Z  @User1,-lf,split_build 2025-12-04T07:56:47.1446669Z  @User2,lf 2025-12-04T07:56:47.1447183Z  @User3,split_build 2025-12-04T07:56:47.1447794Z """ 2025-12-04T07:56:47.1448257Z  2025-12-04T07:56:47.1448720Z import json 2025-12-04T07:56:47.1449246Z import logging 2025-12-04T07:56:47.1449775Z import os 2025-12-04T07:56:47.1450276Z import random 2025-12-04T07:56:47.1450796Z import re 2025-12-04T07:56:47.1451314Z import sys 2025-12-04T07:56:47.1451844Z from argparse import ArgumentParser 2025-12-04T07:56:47.1452633Z from collections.abc import Iterable 2025-12-04T07:56:47.1453319Z from functools import cache 2025-12-04T07:56:47.1453922Z from logging import LogRecord 2025-12-04T07:56:47.1454730Z from typing import Any, NamedTuple 2025-12-04T07:56:47.1455436Z from urllib.request import Request, urlopen 2025-12-04T07:56:47.1456112Z  2025-12-04T07:56:47.1456566Z import yaml 2025-12-04T07:56:47.1457134Z from github import Auth, Github 2025-12-04T07:56:47.1457780Z from github.Issue import Issue 2025-12-04T07:56:47.1458357Z  2025-12-04T07:56:47.1458838Z  2025-12-04T07:56:47.1459360Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T07:56:47.1460222Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T07:56:47.1461231Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T07:56:47.1462070Z  2025-12-04T07:56:47.1462806Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T07:56:47.1463508Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T07:56:47.1464195Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T07:56:47.1465005Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T07:56:47.1465687Z  2025-12-04T07:56:47.1466198Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T07:56:47.1466785Z  2025-12-04T07:56:47.1467330Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T07:56:47.1467915Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T07:56:47.1468496Z  2025-12-04T07:56:47.1468954Z  2025-12-04T07:56:47.1469456Z class Experiment(NamedTuple): 2025-12-04T07:56:47.1470058Z  rollout_perc: float = ( 2025-12-04T07:56:47.1470912Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T07:56:47.1471719Z  ) 2025-12-04T07:56:47.1472206Z  all_branches: bool = ( 2025-12-04T07:56:47.1473050Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T07:56:47.1473823Z  ) 2025-12-04T07:56:47.1474348Z  default: bool = ( 2025-12-04T07:56:47.1475176Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T07:56:47.1475921Z  ) 2025-12-04T07:56:47.1476408Z  2025-12-04T07:56:47.1476880Z  # Add more fields as needed 2025-12-04T07:56:47.1477488Z  2025-12-04T07:56:47.1477910Z  2025-12-04T07:56:47.1478425Z class Settings(NamedTuple): 2025-12-04T07:56:47.1478997Z  """ 2025-12-04T07:56:47.1479616Z  Settings for the experiments that can be opted into. 2025-12-04T07:56:47.1480331Z  """ 2025-12-04T07:56:47.1480792Z  2025-12-04T07:56:47.1481335Z  experiments: dict[str, Experiment] = {} 2025-12-04T07:56:47.1481945Z  2025-12-04T07:56:47.1482521Z  2025-12-04T07:56:47.1483035Z class ColorFormatter(logging.Formatter): 2025-12-04T07:56:47.1483807Z  """Color codes the log messages based on the log level""" 2025-12-04T07:56:47.1484624Z  2025-12-04T07:56:47.1485089Z  COLORS = { 2025-12-04T07:56:47.1485642Z  "WARNING": "\033[33m", # Yellow 2025-12-04T07:56:47.1486307Z  "ERROR": "\033[31m", # Red 2025-12-04T07:56:47.1486961Z  "CRITICAL": "\033[31m", # Red 2025-12-04T07:56:47.1487576Z  "INFO": "\033[0m", # Reset 2025-12-04T07:56:47.1488243Z  "DEBUG": "\033[0m", # Reset 2025-12-04T07:56:47.1488879Z  } 2025-12-04T07:56:47.1489322Z  2025-12-04T07:56:47.1489892Z  def format(self, record: LogRecord) -> str: 2025-12-04T07:56:47.1490793Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T07:56:47.1491732Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T07:56:47.1492438Z  return super().format(record) 2025-12-04T07:56:47.1493089Z  2025-12-04T07:56:47.1493541Z  2025-12-04T07:56:47.1494023Z handler = logging.StreamHandler() 2025-12-04T07:56:47.1495027Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T07:56:47.1495843Z  2025-12-04T07:56:47.1496468Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T07:56:47.1497196Z log.addHandler(handler) 2025-12-04T07:56:47.1497808Z log.setLevel(logging.INFO) 2025-12-04T07:56:47.1498409Z  2025-12-04T07:56:47.1498839Z  2025-12-04T07:56:47.1499416Z def set_github_output(key: str, value: str) -> None: 2025-12-04T07:56:47.1500068Z  """ 2025-12-04T07:56:47.1500778Z  Defines outputs of the github action that invokes this script 2025-12-04T07:56:47.1501599Z  """ 2025-12-04T07:56:47.1502093Z  if not GITHUB_OUTPUT: 2025-12-04T07:56:47.1503363Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T07:56:47.1504643Z  log.warning( 2025-12-04T07:56:47.1505653Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T07:56:47.1506720Z  ) 2025-12-04T07:56:47.1507304Z  print(f"::set-output name={key}::{value}") 2025-12-04T07:56:47.1507961Z  return 2025-12-04T07:56:47.1508483Z  2025-12-04T07:56:47.1509004Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T07:56:47.1509704Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T07:56:47.1510466Z  f.write(f"{key}={value}\n") 2025-12-04T07:56:47.1511050Z  2025-12-04T07:56:47.1511504Z  2025-12-04T07:56:47.1512159Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T07:56:47.1512912Z  return frozenset( 2025-12-04T07:56:47.1513696Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T07:56:47.1514509Z  ) 2025-12-04T07:56:47.1515054Z  2025-12-04T07:56:47.1515472Z  2025-12-04T07:56:47.1515964Z def parse_args() -> Any: 2025-12-04T07:56:47.1516716Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T07:56:47.1517700Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T07:56:47.1518621Z  parser.add_argument( 2025-12-04T07:56:47.1519216Z  "--github-issue-repo", 2025-12-04T07:56:47.1519833Z  type=str, 2025-12-04T07:56:47.1520353Z  required=False, 2025-12-04T07:56:47.1521075Z  default="pytorch/test-infra", 2025-12-04T07:56:47.1521794Z  help="GitHub repo to get the issue", 2025-12-04T07:56:47.1522384Z  ) 2025-12-04T07:56:47.1522930Z  parser.add_argument( 2025-12-04T07:56:47.1523496Z  "--github-repo", 2025-12-04T07:56:47.1524070Z  type=str, 2025-12-04T07:56:47.1524762Z  required=True, 2025-12-04T07:56:47.1525402Z  help="GitHub repo where CI is running", 2025-12-04T07:56:47.1526073Z  ) 2025-12-04T07:56:47.1526587Z  parser.add_argument( 2025-12-04T07:56:47.1527367Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T07:56:47.1528124Z  ) 2025-12-04T07:56:47.1528657Z  parser.add_argument( 2025-12-04T07:56:47.1529425Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T07:56:47.1530268Z  ) 2025-12-04T07:56:47.1530794Z  parser.add_argument( 2025-12-04T07:56:47.1531568Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T07:56:47.1532382Z  ) 2025-12-04T07:56:47.1532877Z  parser.add_argument( 2025-12-04T07:56:47.1533724Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T07:56:47.1534709Z  ) 2025-12-04T07:56:47.1535295Z  parser.add_argument( 2025-12-04T07:56:47.1535915Z  "--github-ref-type", 2025-12-04T07:56:47.1536490Z  type=str, 2025-12-04T07:56:47.1537072Z  required=True, 2025-12-04T07:56:47.1537775Z  help="Current GitHub ref type, branch or tag", 2025-12-04T07:56:47.1538413Z  ) 2025-12-04T07:56:47.1538931Z  parser.add_argument( 2025-12-04T07:56:47.1539664Z  "--eligible-experiments", 2025-12-04T07:56:47.1540311Z  type=_str_comma_separated_to_set, 2025-12-04T07:56:47.1540995Z  required=False, 2025-12-04T07:56:47.1541576Z  default="", 2025-12-04T07:56:47.1542586Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T07:56:47.1543641Z  ) 2025-12-04T07:56:47.1544122Z  parser.add_argument( 2025-12-04T07:56:47.1544957Z  "--opt-out-experiments", 2025-12-04T07:56:47.1545584Z  type=_str_comma_separated_to_set, 2025-12-04T07:56:47.1546294Z  required=False, 2025-12-04T07:56:47.1546844Z  default="", 2025-12-04T07:56:47.1547388Z  help=( 2025-12-04T07:56:47.1548245Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T07:56:47.1549495Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T07:56:47.1550452Z  ), 2025-12-04T07:56:47.1550968Z  ) 2025-12-04T07:56:47.1551469Z  parser.add_argument( 2025-12-04T07:56:47.1552133Z  "--pr-number", 2025-12-04T07:56:47.1552771Z  type=str, 2025-12-04T07:56:47.1553331Z  required=False, 2025-12-04T07:56:47.1553891Z  default="", 2025-12-04T07:56:47.1554722Z  help="the optional PR number where this is run", 2025-12-04T07:56:47.1555437Z  ) 2025-12-04T07:56:47.1555924Z  2025-12-04T07:56:47.1556405Z  return parser.parse_args() 2025-12-04T07:56:47.1557018Z  2025-12-04T07:56:47.1557440Z  2025-12-04T07:56:47.1558203Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T07:56:47.1559226Z  auth = Auth.Token(github_token) 2025-12-04T07:56:47.1559864Z  return Github(auth=auth) 2025-12-04T07:56:47.1560464Z  2025-12-04T07:56:47.1560900Z  2025-12-04T07:56:47.1561680Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T07:56:47.1562635Z  repo = gh.get_repo(repo) 2025-12-04T07:56:47.1563279Z  return repo.get_issue(number=issue_num) 2025-12-04T07:56:47.1563914Z  2025-12-04T07:56:47.1564414Z  2025-12-04T07:56:47.1565011Z def get_potential_pr_author( 2025-12-04T07:56:47.1565796Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T07:56:47.1566611Z ) -> str: 2025-12-04T07:56:47.1568077Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T07:56:47.1569656Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T07:56:47.1571156Z  # embedded in the tag name: ciflow// 2025-12-04T07:56:47.1572318Z  2025-12-04T07:56:47.1573129Z  gh = get_gh_client(github_token) 2025-12-04T07:56:47.1574128Z  2025-12-04T07:56:47.1575268Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T07:56:47.1576534Z  split_tag = ref_name.split("/") 2025-12-04T07:56:47.1577529Z  if ( 2025-12-04T07:56:47.1578423Z  len(split_tag) == 3 2025-12-04T07:56:47.1579442Z  and split_tag[0] == "ciflow" 2025-12-04T07:56:47.1580387Z  and split_tag[2].isnumeric() 2025-12-04T07:56:47.1581046Z  ): 2025-12-04T07:56:47.1581554Z  pr_number = split_tag[2] 2025-12-04T07:56:47.1582181Z  try: 2025-12-04T07:56:47.1582757Z  repository = gh.get_repo(repo) 2025-12-04T07:56:47.1583690Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T07:56:47.1584413Z  except Exception as e: 2025-12-04T07:56:47.1585290Z  raise Exception( # noqa: TRY002 2025-12-04T07:56:47.1586122Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T07:56:47.1586872Z  ) from e 2025-12-04T07:56:47.1587590Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T07:56:47.1588445Z  # In all other cases, return the original input username 2025-12-04T07:56:47.1589167Z  return username 2025-12-04T07:56:47.1589733Z  2025-12-04T07:56:47.1590163Z  2025-12-04T07:56:47.1590730Z def is_exception_branch(branch: str) -> bool: 2025-12-04T07:56:47.1591344Z  """ 2025-12-04T07:56:47.1592192Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T07:56:47.1593067Z  """ 2025-12-04T07:56:47.1593749Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T07:56:47.1594637Z  2025-12-04T07:56:47.1595055Z  2025-12-04T07:56:47.1595548Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T07:56:47.1596199Z  try: 2025-12-04T07:56:47.1596732Z  data = yaml.safe_load(yaml_text) 2025-12-04T07:56:47.1597339Z  return data 2025-12-04T07:56:47.1597938Z  except yaml.YAMLError: 2025-12-04T07:56:47.1598582Z  log.exception("Error loading YAML") 2025-12-04T07:56:47.1599193Z  raise 2025-12-04T07:56:47.1599742Z  2025-12-04T07:56:47.1600155Z  2025-12-04T07:56:47.1600886Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T07:56:47.1601723Z  """ 2025-12-04T07:56:47.1602590Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T07:56:47.1603454Z  2025-12-04T07:56:47.1604123Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:47.1605109Z  and the text below is the list of opted in users. 2025-12-04T07:56:47.1605751Z  2025-12-04T07:56:47.1606460Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T07:56:47.1607257Z  """ 2025-12-04T07:56:47.1607854Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T07:56:47.1608615Z  if len(rollout_state_parts) >= 2: 2025-12-04T07:56:47.1609356Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T07:56:47.1610069Z  else: 2025-12-04T07:56:47.1610543Z  return "", rollout_state 2025-12-04T07:56:47.1611165Z  2025-12-04T07:56:47.1611577Z  2025-12-04T07:56:47.1612097Z class UserOptins(dict[str, list[str]]): 2025-12-04T07:56:47.1612751Z  """ 2025-12-04T07:56:47.1613380Z  Dictionary of users with a list of features they have opted into 2025-12-04T07:56:47.1614131Z  """ 2025-12-04T07:56:47.1614715Z  2025-12-04T07:56:47.1615158Z  2025-12-04T07:56:47.1615795Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T07:56:47.1616587Z  """ 2025-12-04T07:56:47.1617442Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T07:56:47.1618357Z  2025-12-04T07:56:47.1619302Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T07:56:47.1620382Z  - Example line: "@User1,lf,split_build" 2025-12-04T07:56:47.1621307Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T07:56:47.1622069Z  2025-12-04T07:56:47.1622484Z  2025-12-04T07:56:47.1622923Z  """ 2025-12-04T07:56:47.1623400Z  optins = UserOptins() 2025-12-04T07:56:47.1624057Z  for user in user_optin_text.split("\n"): 2025-12-04T07:56:47.1625011Z  user = user.strip("\r\n\t -") 2025-12-04T07:56:47.1625709Z  if not user or not user.startswith("@"): 2025-12-04T07:56:47.1626405Z  # Not a valid user. Skip 2025-12-04T07:56:47.1626989Z  continue 2025-12-04T07:56:47.1627530Z  2025-12-04T07:56:47.1627970Z  if user: 2025-12-04T07:56:47.1628579Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T07:56:47.1629361Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T07:56:47.1630145Z  2025-12-04T07:56:47.1630615Z  return optins 2025-12-04T07:56:47.1631094Z  2025-12-04T07:56:47.1631573Z  2025-12-04T07:56:47.1632172Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T07:56:47.1632885Z  """ 2025-12-04T07:56:47.1633423Z  Check if the experiment name is valid. 2025-12-04T07:56:47.1634062Z  A valid name: 2025-12-04T07:56:47.1635209Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T07:56:47.1636814Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T07:56:47.1637744Z  - Cannot contain spaces 2025-12-04T07:56:47.1638317Z  """ 2025-12-04T07:56:47.1638785Z  2025-12-04T07:56:47.1639371Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T07:56:47.1640203Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T07:56:47.1641068Z  2025-12-04T07:56:47.1641529Z  if valid: 2025-12-04T07:56:47.1642092Z  return True 2025-12-04T07:56:47.1642599Z  2025-12-04T07:56:47.1643060Z  log.error( 2025-12-04T07:56:47.1644764Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-12-04T07:56:47.1646558Z  ) 2025-12-04T07:56:47.1647053Z  return False 2025-12-04T07:56:47.1647561Z  2025-12-04T07:56:47.1648023Z  2025-12-04T07:56:47.1648636Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T07:56:47.1649413Z  """ 2025-12-04T07:56:47.1650176Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T07:56:47.1650999Z  """ 2025-12-04T07:56:47.1651494Z  try: 2025-12-04T07:56:47.1652243Z  if settings_text: 2025-12-04T07:56:47.1653196Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T07:56:47.1654097Z  # for easy reading 2025-12-04T07:56:47.1655169Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T07:56:47.1656164Z  # the backtick character in shell commands. 2025-12-04T07:56:47.1656890Z  backtick = chr(96) # backtick character 2025-12-04T07:56:47.1657705Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T07:56:47.1658500Z  settings = load_yaml(settings_text) 2025-12-04T07:56:47.1659110Z  2025-12-04T07:56:47.1659846Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T07:56:47.1660793Z  experiments = {} 2025-12-04T07:56:47.1661334Z  2025-12-04T07:56:47.1662035Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T07:56:47.1662920Z  if not is_valid_experiment_name(exp_name): 2025-12-04T07:56:47.1664108Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-12-04T07:56:47.1665796Z  continue 2025-12-04T07:56:47.1666333Z  2025-12-04T07:56:47.1666819Z  valid_settings = {} 2025-12-04T07:56:47.1667491Z  for setting in exp_settings: 2025-12-04T07:56:47.1668157Z  if setting not in Experiment._fields: 2025-12-04T07:56:47.1668879Z  log.warning( 2025-12-04T07:56:47.1669707Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T07:56:47.1670539Z  ) 2025-12-04T07:56:47.1671102Z  else: 2025-12-04T07:56:47.1671748Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T07:56:47.1672438Z  2025-12-04T07:56:47.1672993Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T07:56:47.1673782Z  return Settings(experiments) 2025-12-04T07:56:47.1674369Z  2025-12-04T07:56:47.1674903Z  except Exception: 2025-12-04T07:56:47.1675543Z  log.exception("Failed to parse settings") 2025-12-04T07:56:47.1676163Z  2025-12-04T07:56:47.1676645Z  return Settings() 2025-12-04T07:56:47.1677144Z  2025-12-04T07:56:47.1677615Z  2025-12-04T07:56:47.1678257Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T07:56:47.1678930Z  """ 2025-12-04T07:56:47.1679539Z  Parse settings, if any, from the rollout state. 2025-12-04T07:56:47.1680198Z  2025-12-04T07:56:47.1680841Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:47.1681725Z  and the text below is the list of opted in users. 2025-12-04T07:56:47.1682386Z  2025-12-04T07:56:47.1683072Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T07:56:47.1683929Z  """ 2025-12-04T07:56:47.1684797Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:47.1685756Z  return parse_settings_from_text(settings_text) 2025-12-04T07:56:47.1686438Z  2025-12-04T07:56:47.1686847Z  2025-12-04T07:56:47.1687427Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T07:56:47.1688087Z  """ 2025-12-04T07:56:47.1688645Z  Parse users from the rollout state. 2025-12-04T07:56:47.1689265Z  2025-12-04T07:56:47.1689682Z  """ 2025-12-04T07:56:47.1690360Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:47.1691225Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T07:56:47.1691895Z  2025-12-04T07:56:47.1692339Z  2025-12-04T07:56:47.1693069Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:47.1693936Z  """ 2025-12-04T07:56:47.1694476Z  Check if a user is opted into an experiment 2025-12-04T07:56:47.1695185Z  """ 2025-12-04T07:56:47.1695747Z  return experiment_name in user_optins.get(user, []) 2025-12-04T07:56:47.1696592Z  2025-12-04T07:56:47.1697003Z  2025-12-04T07:56:47.1697732Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:47.1698631Z  """ 2025-12-04T07:56:47.1699207Z  Check if a user explicitly opted out of an experiment 2025-12-04T07:56:47.1699879Z  """ 2025-12-04T07:56:47.1700546Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T07:56:47.1701380Z  experiment_optout = "-" + experiment_name 2025-12-04T07:56:47.1702133Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T07:56:47.1793842Z  return False 2025-12-04T07:56:47.1794441Z  2025-12-04T07:56:47.1795157Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T07:56:47.1795816Z  log.warning( 2025-12-04T07:56:47.1796758Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T07:56:47.1797663Z  ) 2025-12-04T07:56:47.1798062Z  2025-12-04T07:56:47.1798432Z  return True 2025-12-04T07:56:47.1798857Z  2025-12-04T07:56:47.1799209Z  2025-12-04T07:56:47.1799593Z def get_runner_prefix( 2025-12-04T07:56:47.1800083Z  rollout_state: str, 2025-12-04T07:56:47.1800619Z  workflow_requestors: Iterable[str], 2025-12-04T07:56:47.1801167Z  branch: str, 2025-12-04T07:56:47.1801730Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:47.1802449Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:47.1803063Z  is_canary: bool = False, 2025-12-04T07:56:47.1803572Z ) -> str: 2025-12-04T07:56:47.1804049Z  settings = parse_settings(rollout_state) 2025-12-04T07:56:47.1804748Z  user_optins = parse_users(rollout_state) 2025-12-04T07:56:47.1805297Z  2025-12-04T07:56:47.1805852Z  fleet_prefix = "" 2025-12-04T07:56:47.1806335Z  prefixes = [] 2025-12-04T07:56:47.1807024Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T07:56:47.1808000Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T07:56:47.1808732Z  log.info( 2025-12-04T07:56:47.1809473Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T07:56:47.1810247Z  ) 2025-12-04T07:56:47.1810672Z  continue 2025-12-04T07:56:47.1811118Z  2025-12-04T07:56:47.1811516Z  if opt_out_experiments: 2025-12-04T07:56:47.1812122Z  if experiment_name in opt_out_experiments: 2025-12-04T07:56:47.1812804Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T07:56:47.1813426Z  log.info( 2025-12-04T07:56:47.1814395Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T07:56:47.1815504Z  ) 2025-12-04T07:56:47.1815958Z  continue 2025-12-04T07:56:47.1816422Z  2025-12-04T07:56:47.1816817Z  if eligible_experiments: 2025-12-04T07:56:47.1817434Z  if experiment_name not in eligible_experiments: 2025-12-04T07:56:47.1818115Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T07:56:47.1818713Z  log.info( 2025-12-04T07:56:47.1819562Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T07:56:47.1820425Z  ) 2025-12-04T07:56:47.1820967Z  continue 2025-12-04T07:56:47.1821520Z  elif not experiment_settings.default: 2025-12-04T07:56:47.1822091Z  log.info( 2025-12-04T07:56:47.1822817Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T07:56:47.1823585Z  ) 2025-12-04T07:56:47.1824015Z  continue 2025-12-04T07:56:47.1824458Z  2025-12-04T07:56:47.1825160Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T07:56:47.1825813Z  opted_out_users = [ 2025-12-04T07:56:47.1826320Z  requestor 2025-12-04T07:56:47.1826838Z  for requestor in workflow_requestors 2025-12-04T07:56:47.1827562Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T07:56:47.1828219Z  ] 2025-12-04T07:56:47.1828640Z  2025-12-04T07:56:47.1829026Z  if opted_out_users: 2025-12-04T07:56:47.1829525Z  log.info( 2025-12-04T07:56:47.1830208Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T07:56:47.1830928Z  ) 2025-12-04T07:56:47.1831365Z  continue 2025-12-04T07:56:47.1831805Z  2025-12-04T07:56:47.1832304Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T07:56:47.1832948Z  opted_in_users = [ 2025-12-04T07:56:47.1833444Z  requestor 2025-12-04T07:56:47.1833961Z  for requestor in workflow_requestors 2025-12-04T07:56:47.1834828Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T07:56:47.1835500Z  ] 2025-12-04T07:56:47.1835899Z  2025-12-04T07:56:47.1836286Z  enabled = False 2025-12-04T07:56:47.1836781Z  if opted_in_users: 2025-12-04T07:56:47.1837368Z  log.info( 2025-12-04T07:56:47.1838036Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T07:56:47.1838747Z  ) 2025-12-04T07:56:47.1839184Z  enabled = True 2025-12-04T07:56:47.1839661Z  2025-12-04T07:56:47.1840093Z  elif experiment_settings.rollout_perc: 2025-12-04T07:56:47.1840957Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T07:56:47.1841942Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T07:56:47.1842613Z  log.info( 2025-12-04T07:56:47.1843532Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T07:56:47.1844461Z  ) 2025-12-04T07:56:47.1844995Z  enabled = True 2025-12-04T07:56:47.1845492Z  2025-12-04T07:56:47.1845866Z  if enabled: 2025-12-04T07:56:47.1846362Z  label = experiment_name 2025-12-04T07:56:47.1846962Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T07:56:47.1847839Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T07:56:47.1848749Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T07:56:47.1849541Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T07:56:47.1850235Z  if is_canary: 2025-12-04T07:56:47.1850781Z  label += CANARY_FLEET_SUFFIX 2025-12-04T07:56:47.1851361Z  fleet_prefix = label 2025-12-04T07:56:47.1851890Z  else: 2025-12-04T07:56:47.1852448Z  prefixes.append(label) 2025-12-04T07:56:47.1852981Z  2025-12-04T07:56:47.1853370Z  if len(prefixes) > 1: 2025-12-04T07:56:47.1853867Z  log.error( 2025-12-04T07:56:47.1855033Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-12-04T07:56:47.1856163Z  ) 2025-12-04T07:56:47.1856604Z  prefixes = prefixes[:1] 2025-12-04T07:56:47.1857109Z  2025-12-04T07:56:47.1857512Z  # Fleet always comes first 2025-12-04T07:56:47.1858035Z  if fleet_prefix: 2025-12-04T07:56:47.1858541Z  prefixes.insert(0, fleet_prefix) 2025-12-04T07:56:47.1859068Z  2025-12-04T07:56:47.1859564Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T07:56:47.1860159Z  2025-12-04T07:56:47.1860523Z  2025-12-04T07:56:47.1861202Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T07:56:47.1861984Z  """ 2025-12-04T07:56:47.1862627Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T07:56:47.1863350Z  2025-12-04T07:56:47.1863957Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T07:56:47.1864732Z  """ 2025-12-04T07:56:47.1865180Z  gh = get_gh_client(github_token) 2025-12-04T07:56:47.1865780Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T07:56:47.1866465Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T07:56:47.1867093Z  2025-12-04T07:56:47.1867450Z  2025-12-04T07:56:47.1868072Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T07:56:47.1868918Z  for _ in range(num_retries): 2025-12-04T07:56:47.1869438Z  try: 2025-12-04T07:56:47.1869926Z  req = Request(url=url, headers=headers) 2025-12-04T07:56:47.1870626Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T07:56:47.1871319Z  return json.loads(content) 2025-12-04T07:56:47.1871874Z  except Exception as e: 2025-12-04T07:56:47.1872477Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T07:56:47.1873049Z  2025-12-04T07:56:47.1873659Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T07:56:47.1874401Z  return {} 2025-12-04T07:56:47.1875042Z  2025-12-04T07:56:47.1875409Z  2025-12-04T07:56:47.1875770Z @cache 2025-12-04T07:56:47.1876448Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T07:56:47.1877232Z  """ 2025-12-04T07:56:47.1877682Z  Dynamically get PR information 2025-12-04T07:56:47.1878201Z  """ 2025-12-04T07:56:47.1878745Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T07:56:47.1879407Z  headers = { 2025-12-04T07:56:47.1879932Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T07:56:47.1880579Z  "Authorization": f"token {github_token}", 2025-12-04T07:56:47.1881136Z  } 2025-12-04T07:56:47.1881619Z  json_response: dict[str, Any] = download_json( 2025-12-04T07:56:47.1882269Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T07:56:47.1882848Z  headers=headers, 2025-12-04T07:56:47.1883337Z  ) 2025-12-04T07:56:47.1883712Z  2025-12-04T07:56:47.1884106Z  if not json_response: 2025-12-04T07:56:47.1884817Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T07:56:47.1885558Z  return {} 2025-12-04T07:56:47.1885996Z  2025-12-04T07:56:47.1886383Z  return json_response 2025-12-04T07:56:47.1886852Z  2025-12-04T07:56:47.1887214Z  2025-12-04T07:56:47.1887834Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T07:56:47.1888582Z  """ 2025-12-04T07:56:47.1889168Z  Dynamically get the latest list of labels from the pull request 2025-12-04T07:56:47.1889829Z  """ 2025-12-04T07:56:47.1890360Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T07:56:47.1891018Z  return { 2025-12-04T07:56:47.1891648Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T07:56:47.1892351Z  } 2025-12-04T07:56:47.1892747Z  2025-12-04T07:56:47.1893107Z  2025-12-04T07:56:47.1893482Z def main() -> None: 2025-12-04T07:56:47.1893965Z  args = parse_args() 2025-12-04T07:56:47.1894428Z  2025-12-04T07:56:47.1894984Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T07:56:47.1895543Z  2025-12-04T07:56:47.1895944Z  # Check if the PR is opt-out 2025-12-04T07:56:47.1896486Z  if args.pr_number: 2025-12-04T07:56:47.1897203Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T07:56:47.1897987Z  if OPT_OUT_LABEL in labels: 2025-12-04T07:56:47.1898518Z  log.info( 2025-12-04T07:56:47.1899263Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T07:56:47.1900047Z  ) 2025-12-04T07:56:47.1900671Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:47.1901382Z  sys.exit() 2025-12-04T07:56:47.1901912Z  2025-12-04T07:56:47.1902283Z  try: 2025-12-04T07:56:47.1902768Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T07:56:47.1903518Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T07:56:47.1904176Z  ) 2025-12-04T07:56:47.1904853Z  2025-12-04T07:56:47.1905281Z  username = get_potential_pr_author( 2025-12-04T07:56:47.1905862Z  args.github_token, 2025-12-04T07:56:47.1906391Z  args.github_repo, 2025-12-04T07:56:47.1906923Z  args.github_actor, 2025-12-04T07:56:47.1907469Z  args.github_ref_type, 2025-12-04T07:56:47.1908005Z  args.github_branch, 2025-12-04T07:56:47.1908508Z  ) 2025-12-04T07:56:47.1908904Z  2025-12-04T07:56:47.1909411Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T07:56:47.1910048Z  2025-12-04T07:56:47.1910489Z  runner_label_prefix = get_runner_prefix( 2025-12-04T07:56:47.1911095Z  rollout_state, 2025-12-04T07:56:47.1911644Z  (args.github_issue_owner, username), 2025-12-04T07:56:47.1912228Z  args.github_branch, 2025-12-04T07:56:47.1912784Z  args.eligible_experiments, 2025-12-04T07:56:47.1913366Z  args.opt_out_experiments, 2025-12-04T07:56:47.1913911Z  is_canary, 2025-12-04T07:56:47.1914374Z  ) 2025-12-04T07:56:47.1914989Z  2025-12-04T07:56:47.1915386Z  except Exception as e: 2025-12-04T07:56:47.1915912Z  log.error( 2025-12-04T07:56:47.1916640Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T07:56:47.1917505Z  ) 2025-12-04T07:56:47.1917904Z  2025-12-04T07:56:47.1918464Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:47.1919144Z  2025-12-04T07:56:47.1919505Z  2025-12-04T07:56:47.1919894Z if __name__ == "__main__": 2025-12-04T07:56:47.1920382Z  main() 2025-12-04T07:56:47.1920794Z  2025-12-04T07:56:47.1921155Z EOF 2025-12-04T07:56:47.1921533Z  2025-12-04T07:56:47.1921925Z cat runner_determinator.py 2025-12-04T07:56:47.4687574Z shell: /usr/bin/bash -e {0} 2025-12-04T07:56:47.4688788Z env: 2025-12-04T07:56:47.4689904Z GITHUB_TOKEN: *** 2025-12-04T07:56:47.4690625Z ISSUE_NUMBER: 5132 2025-12-04T07:56:47.4691447Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T07:56:47.4692345Z ISSUE_OWNER: 2025-12-04T07:56:47.4693040Z CHECK_EXPERIMENTS: 2025-12-04T07:56:47.4693801Z OPT_OUT_EXPERIMENTS: 2025-12-04T07:56:47.4694550Z PR_NUMBER: 2025-12-04T07:56:47.4695374Z ##[endgroup] 2025-12-04T07:56:47.4903365Z # flake8: noqa: G004 2025-12-04T07:56:47.4904047Z 2025-12-04T07:56:47.4905208Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T07:56:47.4907357Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T07:56:47.4909203Z # python .github/scripts/update_runner_determinator.py 2025-12-04T07:56:47.4910227Z 2025-12-04T07:56:47.4910650Z """ 2025-12-04T07:56:47.4911993Z This runner determinator is used to determine which set of runners to run a 2025-12-04T07:56:47.4914008Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T07:56:47.4916357Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T07:56:47.4918309Z of which runners should be used to run which job. 2025-12-04T07:56:47.4919254Z 2025-12-04T07:56:47.4920143Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T07:56:47.4922517Z separated by a line containing "---". If the line is not present, the 2025-12-04T07:56:47.4924891Z settings are considered to be empty with only the second part, the user 2025-12-04T07:56:47.4926687Z list, defined. 2025-12-04T07:56:47.4927249Z 2025-12-04T07:56:47.4928163Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T07:56:47.4930544Z used to define any settings that are needed to determine which runners to use. 2025-12-04T07:56:47.4932705Z It's fields are defined by the RolloutSettings class below. 2025-12-04T07:56:47.4933821Z 2025-12-04T07:56:47.4934924Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T07:56:47.4937199Z The user list is also a comma separated list of additional features or 2025-12-04T07:56:47.4939075Z experiments which the user could be opted in to. 2025-12-04T07:56:47.4940084Z 2025-12-04T07:56:47.4940578Z The user list has the following rules: 2025-12-04T07:56:47.4941451Z 2025-12-04T07:56:47.4942225Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T07:56:47.4944311Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T07:56:47.4946314Z - A "#" prefix opts the user out of all experiments 2025-12-04T07:56:47.4947296Z 2025-12-04T07:56:47.4947736Z Example config: 2025-12-04T07:56:47.4948835Z # A list of experiments that can be opted into. 2025-12-04T07:56:47.4950454Z # This defines the behavior they'll induce when opted into. 2025-12-04T07:56:47.4951981Z # Expected syntax is: 2025-12-04T07:56:47.4953466Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T07:56:47.4955989Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T07:56:47.4957481Z 2025-12-04T07:56:47.4957865Z experiments: 2025-12-04T07:56:47.4958735Z lf: 2025-12-04T07:56:47.4959547Z rollout_percent: 25 2025-12-04T07:56:47.4960719Z all_branches: false 2025-12-04T07:56:47.4961697Z default: true 2025-12-04T07:56:47.4962582Z --- 2025-12-04T07:56:47.4962999Z 2025-12-04T07:56:47.4963342Z # Opt-ins: 2025-12-04T07:56:47.4964783Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T07:56:47.4966836Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T07:56:47.4968660Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T07:56:47.4970207Z # Experiments should be from the above list. 2025-12-04T07:56:47.4971090Z 2025-12-04T07:56:47.4971486Z @User1,-lf,split_build 2025-12-04T07:56:47.4972462Z @User2,lf 2025-12-04T07:56:47.4973293Z @User3,split_build 2025-12-04T07:56:47.4974191Z """ 2025-12-04T07:56:47.4974777Z 2025-12-04T07:56:47.4975130Z import json 2025-12-04T07:56:47.4975928Z import logging 2025-12-04T07:56:47.4976750Z import os 2025-12-04T07:56:47.4977540Z import random 2025-12-04T07:56:47.4978365Z import re 2025-12-04T07:56:47.4979130Z import sys 2025-12-04T07:56:47.4980017Z from argparse import ArgumentParser 2025-12-04T07:56:47.4981201Z from collections.abc import Iterable 2025-12-04T07:56:47.4982362Z from functools import cache 2025-12-04T07:56:47.4983380Z from logging import LogRecord 2025-12-04T07:56:47.4984482Z from typing import Any, NamedTuple 2025-12-04T07:56:47.4985811Z from urllib.request import Request, urlopen 2025-12-04T07:56:47.4986644Z 2025-12-04T07:56:47.4987005Z import yaml 2025-12-04T07:56:47.4987841Z from github import Auth, Github 2025-12-04T07:56:47.4988912Z from github.Issue import Issue 2025-12-04T07:56:47.4989583Z 2025-12-04T07:56:47.4989592Z 2025-12-04T07:56:47.4990069Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T07:56:47.4991590Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T07:56:47.4994002Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T07:56:47.4996133Z 2025-12-04T07:56:47.4996953Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T07:56:47.4998557Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T07:56:47.4999725Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T07:56:47.5000988Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T07:56:47.5001783Z 2025-12-04T07:56:47.5002210Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T07:56:47.5002962Z 2025-12-04T07:56:47.5003351Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T07:56:47.5004353Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T07:56:47.5005187Z 2025-12-04T07:56:47.5005197Z 2025-12-04T07:56:47.5005607Z class Experiment(NamedTuple): 2025-12-04T07:56:47.5006680Z rollout_perc: float = ( 2025-12-04T07:56:47.5008107Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T07:56:47.5009648Z ) 2025-12-04T07:56:47.5010452Z all_branches: bool = ( 2025-12-04T07:56:47.5011835Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T07:56:47.5013348Z ) 2025-12-04T07:56:47.5014124Z default: bool = ( 2025-12-04T07:56:47.5015498Z True # If True, the experiment is enabled by default for all queries 2025-12-04T07:56:47.5016881Z ) 2025-12-04T07:56:47.5017278Z 2025-12-04T07:56:47.5017634Z # Add more fields as needed 2025-12-04T07:56:47.5018280Z 2025-12-04T07:56:47.5018287Z 2025-12-04T07:56:47.5018662Z class Settings(NamedTuple): 2025-12-04T07:56:47.5019941Z """ 2025-12-04T07:56:47.5021493Z Settings for the experiments that can be opted into. 2025-12-04T07:56:47.5022861Z """ 2025-12-04T07:56:47.5023480Z 2025-12-04T07:56:47.5024157Z experiments: dict[str, Experiment] = {} 2025-12-04T07:56:47.5025375Z 2025-12-04T07:56:47.5025386Z 2025-12-04T07:56:47.5025839Z class ColorFormatter(logging.Formatter): 2025-12-04T07:56:47.5027234Z """Color codes the log messages based on the log level""" 2025-12-04T07:56:47.5028201Z 2025-12-04T07:56:47.5028537Z COLORS = { 2025-12-04T07:56:47.5029347Z "WARNING": "\033[33m", # Yellow 2025-12-04T07:56:47.5030612Z "ERROR": "\033[31m", # Red 2025-12-04T07:56:47.5031687Z "CRITICAL": "\033[31m", # Red 2025-12-04T07:56:47.5032756Z "INFO": "\033[0m", # Reset 2025-12-04T07:56:47.5033763Z "DEBUG": "\033[0m", # Reset 2025-12-04T07:56:47.5034860Z } 2025-12-04T07:56:47.5035256Z 2025-12-04T07:56:47.5035693Z def format(self, record: LogRecord) -> str: 2025-12-04T07:56:47.5037327Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T07:56:47.5039079Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T07:56:47.5040841Z return super().format(record) 2025-12-04T07:56:47.5041558Z 2025-12-04T07:56:47.5041567Z 2025-12-04T07:56:47.5041970Z handler = logging.StreamHandler() 2025-12-04T07:56:47.5043504Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T07:56:47.5044872Z 2025-12-04T07:56:47.5045368Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T07:56:47.5046599Z log.addHandler(handler) 2025-12-04T07:56:47.5047521Z log.setLevel(logging.INFO) 2025-12-04T07:56:47.5048116Z 2025-12-04T07:56:47.5048123Z 2025-12-04T07:56:47.5048622Z def set_github_output(key: str, value: str) -> None: 2025-12-04T07:56:47.5049800Z """ 2025-12-04T07:56:47.5050824Z Defines outputs of the github action that invokes this script 2025-12-04T07:56:47.5052157Z """ 2025-12-04T07:56:47.5052886Z if not GITHUB_OUTPUT: 2025-12-04T07:56:47.5055414Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T07:56:47.5057990Z log.warning( 2025-12-04T07:56:47.5059802Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T07:56:47.5061830Z ) 2025-12-04T07:56:47.5072133Z print(f"::set-output name={key}::{value}") 2025-12-04T07:56:47.5073325Z return 2025-12-04T07:56:47.5073808Z 2025-12-04T07:56:47.5074360Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T07:56:47.5075838Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T07:56:47.5077034Z f.write(f"{key}={value}\n") 2025-12-04T07:56:47.5077724Z 2025-12-04T07:56:47.5077732Z 2025-12-04T07:56:47.5078355Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T07:56:47.5079696Z return frozenset( 2025-12-04T07:56:47.5080955Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T07:56:47.5082421Z ) 2025-12-04T07:56:47.5082811Z 2025-12-04T07:56:47.5082820Z 2025-12-04T07:56:47.5083165Z def parse_args() -> Any: 2025-12-04T07:56:47.5084291Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T07:56:47.5086257Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T07:56:47.5087944Z parser.add_argument( 2025-12-04T07:56:47.5088858Z "--github-issue-repo", 2025-12-04T07:56:47.5089797Z type=str, 2025-12-04T07:56:47.5090586Z required=False, 2025-12-04T07:56:47.5091487Z default="pytorch/test-infra", 2025-12-04T07:56:47.5092584Z help="GitHub repo to get the issue", 2025-12-04T07:56:47.5093622Z ) 2025-12-04T07:56:47.5094338Z parser.add_argument( 2025-12-04T07:56:47.5095378Z "--github-repo", 2025-12-04T07:56:47.5096241Z type=str, 2025-12-04T07:56:47.5097010Z required=True, 2025-12-04T07:56:47.5097933Z help="GitHub repo where CI is running", 2025-12-04T07:56:47.5098995Z ) 2025-12-04T07:56:47.5099712Z parser.add_argument( 2025-12-04T07:56:47.5100964Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T07:56:47.5102379Z ) 2025-12-04T07:56:47.5103103Z parser.add_argument( 2025-12-04T07:56:47.5104393Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T07:56:47.5106002Z ) 2025-12-04T07:56:47.5106716Z parser.add_argument( 2025-12-04T07:56:47.5108168Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T07:56:47.5109611Z ) 2025-12-04T07:56:47.5110337Z parser.add_argument( 2025-12-04T07:56:47.5111686Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T07:56:47.5113190Z ) 2025-12-04T07:56:47.5113911Z parser.add_argument( 2025-12-04T07:56:47.5114948Z "--github-ref-type", 2025-12-04T07:56:47.5115854Z type=str, 2025-12-04T07:56:47.5116627Z required=True, 2025-12-04T07:56:47.5117607Z help="Current GitHub ref type, branch or tag", 2025-12-04T07:56:47.5118718Z ) 2025-12-04T07:56:47.5119444Z parser.add_argument( 2025-12-04T07:56:47.5120361Z "--eligible-experiments", 2025-12-04T07:56:47.5121398Z type=_str_comma_separated_to_set, 2025-12-04T07:56:47.5122442Z required=False, 2025-12-04T07:56:47.5123269Z default="", 2025-12-04T07:56:47.5125089Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T07:56:47.5127091Z ) 2025-12-04T07:56:47.5127810Z parser.add_argument( 2025-12-04T07:56:47.5128721Z "--opt-out-experiments", 2025-12-04T07:56:47.5129732Z type=_str_comma_separated_to_set, 2025-12-04T07:56:47.5130776Z required=False, 2025-12-04T07:56:47.5131599Z default="", 2025-12-04T07:56:47.5132352Z help=( 2025-12-04T07:56:47.5133722Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T07:56:47.5136161Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T07:56:47.5137919Z ), 2025-12-04T07:56:47.5138612Z ) 2025-12-04T07:56:47.5139316Z parser.add_argument( 2025-12-04T07:56:47.5140187Z "--pr-number", 2025-12-04T07:56:47.5140992Z type=str, 2025-12-04T07:56:47.5141759Z required=False, 2025-12-04T07:56:47.5142580Z default="", 2025-12-04T07:56:47.5143605Z help="the optional PR number where this is run", 2025-12-04T07:56:47.5144790Z ) 2025-12-04T07:56:47.5145338Z 2025-12-04T07:56:47.5145790Z return parser.parse_args() 2025-12-04T07:56:47.5146429Z 2025-12-04T07:56:47.5146437Z 2025-12-04T07:56:47.5147272Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T07:56:47.5148801Z auth = Auth.Token(github_token) 2025-12-04T07:56:47.5149800Z return Github(auth=auth) 2025-12-04T07:56:47.5150378Z 2025-12-04T07:56:47.5150386Z 2025-12-04T07:56:47.5151301Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T07:56:47.5152926Z repo = gh.get_repo(repo) 2025-12-04T07:56:47.5153884Z return repo.get_issue(number=issue_num) 2025-12-04T07:56:47.5154671Z 2025-12-04T07:56:47.5154680Z 2025-12-04T07:56:47.5155026Z def get_potential_pr_author( 2025-12-04T07:56:47.5156298Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T07:56:47.5157642Z ) -> str: 2025-12-04T07:56:47.5158656Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T07:56:47.5160273Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T07:56:47.5161743Z # embedded in the tag name: ciflow// 2025-12-04T07:56:47.5162580Z 2025-12-04T07:56:47.5162946Z gh = get_gh_client(github_token) 2025-12-04T07:56:47.5163597Z 2025-12-04T07:56:47.5164119Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T07:56:47.5165405Z split_tag = ref_name.split("/") 2025-12-04T07:56:47.5166360Z if ( 2025-12-04T07:56:47.5167090Z len(split_tag) == 3 2025-12-04T07:56:47.5168022Z and split_tag[0] == "ciflow" 2025-12-04T07:56:47.5169068Z and split_tag[2].isnumeric() 2025-12-04T07:56:47.5170031Z ): 2025-12-04T07:56:47.5170763Z pr_number = split_tag[2] 2025-12-04T07:56:47.5171818Z try: 2025-12-04T07:56:47.5172667Z repository = gh.get_repo(repo) 2025-12-04T07:56:47.5173907Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T07:56:47.5175174Z except Exception as e: 2025-12-04T07:56:47.5176202Z raise Exception( # noqa: TRY002 2025-12-04T07:56:47.5177561Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T07:56:47.5178863Z ) from e 2025-12-04T07:56:47.5179920Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T07:56:47.5181348Z # In all other cases, return the original input username 2025-12-04T07:56:47.5182518Z return username 2025-12-04T07:56:47.5182992Z 2025-12-04T07:56:47.5183000Z 2025-12-04T07:56:47.5183429Z def is_exception_branch(branch: str) -> bool: 2025-12-04T07:56:47.5184495Z """ 2025-12-04T07:56:47.5185826Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T07:56:47.5187434Z """ 2025-12-04T07:56:47.5188503Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T07:56:47.5189581Z 2025-12-04T07:56:47.5189590Z 2025-12-04T07:56:47.5189969Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T07:56:47.5190947Z try: 2025-12-04T07:56:47.5191686Z data = yaml.safe_load(yaml_text) 2025-12-04T07:56:47.5192694Z return data 2025-12-04T07:56:47.5193483Z except yaml.YAMLError: 2025-12-04T07:56:47.5194431Z log.exception("Error loading YAML") 2025-12-04T07:56:47.5195483Z raise 2025-12-04T07:56:47.5195903Z 2025-12-04T07:56:47.5195910Z 2025-12-04T07:56:47.5196724Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T07:56:47.5198182Z """ 2025-12-04T07:56:47.5199414Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T07:56:47.5200639Z 2025-12-04T07:56:47.5201417Z If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:47.5202907Z and the text below is the list of opted in users. 2025-12-04T07:56:47.5203719Z 2025-12-04T07:56:47.5204462Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T07:56:47.5206159Z """ 2025-12-04T07:56:47.5207022Z rollout_state_parts = rollout_state.split("---") 2025-12-04T07:56:47.5208194Z if len(rollout_state_parts) >= 2: 2025-12-04T07:56:47.5209381Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T07:56:47.5210545Z else: 2025-12-04T07:56:47.5211266Z return "", rollout_state 2025-12-04T07:56:47.5211866Z 2025-12-04T07:56:47.5211876Z 2025-12-04T07:56:47.5212268Z class UserOptins(dict[str, list[str]]): 2025-12-04T07:56:47.5213253Z """ 2025-12-04T07:56:47.5214276Z Dictionary of users with a list of features they have opted into 2025-12-04T07:56:47.5215609Z """ 2025-12-04T07:56:47.5216011Z 2025-12-04T07:56:47.5216019Z 2025-12-04T07:56:47.5216680Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T07:56:47.5217978Z """ 2025-12-04T07:56:47.5219402Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T07:56:47.5220825Z 2025-12-04T07:56:47.5222097Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T07:56:47.5224143Z - Example line: "@User1,lf,split_build" 2025-12-04T07:56:47.5225561Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T07:56:47.5226528Z 2025-12-04T07:56:47.5226536Z 2025-12-04T07:56:47.5226842Z """ 2025-12-04T07:56:47.5227556Z optins = UserOptins() 2025-12-04T07:56:47.5228500Z for user in user_optin_text.split("\n"): 2025-12-04T07:56:47.5229581Z user = user.strip("\r\n\t -") 2025-12-04T07:56:47.5230640Z if not user or not user.startswith("@"): 2025-12-04T07:56:47.5231816Z # Not a valid user. Skip 2025-12-04T07:56:47.5232768Z continue 2025-12-04T07:56:47.5233240Z 2025-12-04T07:56:47.5233537Z if user: 2025-12-04T07:56:47.5234379Z usr_name = user.split(",")[0].strip("@") 2025-12-04T07:56:47.5235813Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T07:56:47.5236801Z 2025-12-04T07:56:47.5237115Z return optins 2025-12-04T07:56:47.5237576Z 2025-12-04T07:56:47.5237584Z 2025-12-04T07:56:47.5238141Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T07:56:47.5239322Z """ 2025-12-04T07:56:47.5240088Z Check if the experiment name is valid. 2025-12-04T07:56:47.5241100Z A valid name: 2025-12-04T07:56:47.5242346Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T07:56:47.5244210Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T07:56:47.5245679Z - Cannot contain spaces 2025-12-04T07:56:47.5246581Z """ 2025-12-04T07:56:47.5246958Z 2025-12-04T07:56:47.5247452Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T07:56:47.5248811Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T07:56:47.5249680Z 2025-12-04T07:56:47.5249993Z if valid: 2025-12-04T07:56:47.5250718Z return True 2025-12-04T07:56:47.5251178Z 2025-12-04T07:56:47.5251492Z log.error( 2025-12-04T07:56:47.5254414Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-12-04T07:56:47.5257689Z ) 2025-12-04T07:56:47.5258364Z return False 2025-12-04T07:56:47.5258819Z 2025-12-04T07:56:47.5258826Z 2025-12-04T07:56:47.5259404Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T07:56:47.5260623Z """ 2025-12-04T07:56:47.5261855Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T07:56:47.5263269Z """ 2025-12-04T07:56:47.5263932Z try: 2025-12-04T07:56:47.5264719Z if settings_text: 2025-12-04T07:56:47.5266136Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T07:56:47.5267721Z # for easy reading 2025-12-04T07:56:47.5269281Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T07:56:47.5271079Z # the backtick character in shell commands. 2025-12-04T07:56:47.5272261Z backtick = chr(96) # backtick character 2025-12-04T07:56:47.5273559Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T07:56:47.5274907Z settings = load_yaml(settings_text) 2025-12-04T07:56:47.5275621Z 2025-12-04T07:56:47.5276424Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T07:56:47.5277925Z experiments = {} 2025-12-04T07:56:47.5278493Z 2025-12-04T07:56:47.5279210Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T07:56:47.5280699Z if not is_valid_experiment_name(exp_name): 2025-12-04T07:56:47.5282916Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-12-04T07:56:47.5285094Z continue 2025-12-04T07:56:47.5285655Z 2025-12-04T07:56:47.5285999Z valid_settings = {} 2025-12-04T07:56:47.5287038Z for setting in exp_settings: 2025-12-04T07:56:47.5288154Z if setting not in Experiment._fields: 2025-12-04T07:56:47.5289235Z log.warning( 2025-12-04T07:56:47.5290599Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T07:56:47.5292096Z ) 2025-12-04T07:56:47.5292917Z else: 2025-12-04T07:56:47.5293927Z valid_settings[setting] = exp_settings[setting] 2025-12-04T07:56:47.5294804Z 2025-12-04T07:56:47.5295329Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T07:56:47.5296572Z return Settings(experiments) 2025-12-04T07:56:47.5297249Z 2025-12-04T07:56:47.5297583Z except Exception: 2025-12-04T07:56:47.5298483Z log.exception("Failed to parse settings") 2025-12-04T07:56:47.5299223Z 2025-12-04T07:56:47.5299555Z return Settings() 2025-12-04T07:56:47.5300043Z 2025-12-04T07:56:47.5300051Z 2025-12-04T07:56:47.5300514Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T07:56:47.5301602Z """ 2025-12-04T07:56:47.5302411Z Parse settings, if any, from the rollout state. 2025-12-04T07:56:47.5303193Z 2025-12-04T07:56:47.5303849Z If the issue body contains "---" then the text above that is the settings 2025-12-04T07:56:47.5305373Z and the text below is the list of opted in users. 2025-12-04T07:56:47.5306148Z 2025-12-04T07:56:47.5306911Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T07:56:47.5308322Z """ 2025-12-04T07:56:47.5309357Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:47.5310825Z return parse_settings_from_text(settings_text) 2025-12-04T07:56:47.5311593Z 2025-12-04T07:56:47.5311600Z 2025-12-04T07:56:47.5312087Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T07:56:47.5313161Z """ 2025-12-04T07:56:47.5313910Z Parse users from the rollout state. 2025-12-04T07:56:47.5314626Z 2025-12-04T07:56:47.5314918Z """ 2025-12-04T07:56:47.5315913Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T07:56:47.5317319Z return parse_user_opt_in_from_text(users_text) 2025-12-04T07:56:47.5318090Z 2025-12-04T07:56:47.5318097Z 2025-12-04T07:56:47.5318935Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:47.5320368Z """ 2025-12-04T07:56:47.5321142Z Check if a user is opted into an experiment 2025-12-04T07:56:47.5322162Z """ 2025-12-04T07:56:47.5323009Z return experiment_name in user_optins.get(user, []) 2025-12-04T07:56:47.5323824Z 2025-12-04T07:56:47.5323832Z 2025-12-04T07:56:47.5324676Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T07:56:47.5326122Z """ 2025-12-04T07:56:47.5326991Z Check if a user explicitly opted out of an experiment 2025-12-04T07:56:47.5328077Z """ 2025-12-04T07:56:47.5329031Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T07:56:47.5330339Z experiment_optout = "-" + experiment_name 2025-12-04T07:56:47.5331552Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T07:56:47.5332703Z return False 2025-12-04T07:56:47.5333174Z 2025-12-04T07:56:47.5333695Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T07:56:47.5334893Z log.warning( 2025-12-04T07:56:47.5336410Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T07:56:47.5338116Z ) 2025-12-04T07:56:47.5338497Z 2025-12-04T07:56:47.5338806Z return True 2025-12-04T07:56:47.5339235Z 2025-12-04T07:56:47.5339243Z 2025-12-04T07:56:47.5339567Z def get_runner_prefix( 2025-12-04T07:56:47.5340382Z rollout_state: str, 2025-12-04T07:56:47.5341259Z workflow_requestors: Iterable[str], 2025-12-04T07:56:47.5342237Z branch: str, 2025-12-04T07:56:47.5343146Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:47.5344405Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T07:56:47.5345577Z is_canary: bool = False, 2025-12-04T07:56:47.5346433Z ) -> str: 2025-12-04T07:56:47.5347303Z settings = parse_settings(rollout_state) 2025-12-04T07:56:47.5348402Z user_optins = parse_users(rollout_state) 2025-12-04T07:56:47.5349111Z 2025-12-04T07:56:47.5349442Z fleet_prefix = "" 2025-12-04T07:56:47.5350227Z prefixes = [] 2025-12-04T07:56:47.5351432Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T07:56:47.5353198Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T07:56:47.5354549Z log.info( 2025-12-04T07:56:47.5355898Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T07:56:47.5357333Z ) 2025-12-04T07:56:47.5358038Z continue 2025-12-04T07:56:47.5358499Z 2025-12-04T07:56:47.5358839Z if opt_out_experiments: 2025-12-04T07:56:47.5359832Z if experiment_name in opt_out_experiments: 2025-12-04T07:56:47.5361010Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T07:56:47.5362123Z log.info( 2025-12-04T07:56:47.5363851Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T07:56:47.5365772Z ) 2025-12-04T07:56:47.5366517Z continue 2025-12-04T07:56:47.5367022Z 2025-12-04T07:56:47.5367372Z if eligible_experiments: 2025-12-04T07:56:47.5368426Z if experiment_name not in eligible_experiments: 2025-12-04T07:56:47.5369629Z exp_list = ", ".join(eligible_experiments) 2025-12-04T07:56:47.5370675Z log.info( 2025-12-04T07:56:47.5372157Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T07:56:47.5373753Z ) 2025-12-04T07:56:47.5374502Z continue 2025-12-04T07:56:47.5375454Z elif not experiment_settings.default: 2025-12-04T07:56:47.5376460Z log.info( 2025-12-04T07:56:47.5377989Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T07:56:47.5379450Z ) 2025-12-04T07:56:47.5380158Z continue 2025-12-04T07:56:47.5380623Z 2025-12-04T07:56:47.5381143Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T07:56:47.5382301Z opted_out_users = [ 2025-12-04T07:56:47.5383141Z requestor 2025-12-04T07:56:47.5383999Z for requestor in workflow_requestors 2025-12-04T07:56:47.5385312Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T07:56:47.5386518Z ] 2025-12-04T07:56:47.5386896Z 2025-12-04T07:56:47.5387226Z if opted_out_users: 2025-12-04T07:56:47.5388055Z log.info( 2025-12-04T07:56:47.5389208Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T07:56:47.5390519Z ) 2025-12-04T07:56:47.5391220Z continue 2025-12-04T07:56:47.5391696Z 2025-12-04T07:56:47.5392208Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T07:56:47.5393355Z opted_in_users = [ 2025-12-04T07:56:47.5394181Z requestor 2025-12-04T07:56:47.5395101Z for requestor in workflow_requestors 2025-12-04T07:56:47.5396327Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T07:56:47.5397481Z ] 2025-12-04T07:56:47.5397862Z 2025-12-04T07:56:47.5398174Z enabled = False 2025-12-04T07:56:47.5398972Z if opted_in_users: 2025-12-04T07:56:47.5399783Z log.info( 2025-12-04T07:56:47.5400892Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T07:56:47.5402184Z ) 2025-12-04T07:56:47.5402896Z enabled = True 2025-12-04T07:56:47.5403411Z 2025-12-04T07:56:47.5403819Z elif experiment_settings.rollout_perc: 2025-12-04T07:56:47.5405443Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T07:56:47.5407328Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T07:56:47.5408536Z log.info( 2025-12-04T07:56:47.5410149Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T07:56:47.5411887Z ) 2025-12-04T07:56:47.5412624Z enabled = True 2025-12-04T07:56:47.5413178Z 2025-12-04T07:56:47.5413484Z if enabled: 2025-12-04T07:56:47.5414256Z label = experiment_name 2025-12-04T07:56:47.5415328Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T07:56:47.5416859Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T07:56:47.5418509Z # - If it's enabled, then we always list it's prefix first 2025-12-04T07:56:47.5419907Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T07:56:47.5421146Z if is_canary: 2025-12-04T07:56:47.5422050Z label += CANARY_FLEET_SUFFIX 2025-12-04T07:56:47.5423050Z fleet_prefix = label 2025-12-04T07:56:47.5423955Z else: 2025-12-04T07:56:47.5424784Z prefixes.append(label) 2025-12-04T07:56:47.5425439Z 2025-12-04T07:56:47.5425765Z if len(prefixes) > 1: 2025-12-04T07:56:47.5426569Z log.error( 2025-12-04T07:56:47.5428703Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-12-04T07:56:47.5430827Z ) 2025-12-04T07:56:47.5431531Z prefixes = prefixes[:1] 2025-12-04T07:56:47.5432091Z 2025-12-04T07:56:47.5432446Z # Fleet always comes first 2025-12-04T07:56:47.5433313Z if fleet_prefix: 2025-12-04T07:56:47.5434139Z prefixes.insert(0, fleet_prefix) 2025-12-04T07:56:47.5434981Z 2025-12-04T07:56:47.5435516Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T07:56:47.5436278Z 2025-12-04T07:56:47.5436287Z 2025-12-04T07:56:47.5437071Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T07:56:47.5438488Z """ 2025-12-04T07:56:47.5439560Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T07:56:47.5440616Z 2025-12-04T07:56:47.5441322Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T07:56:47.5442620Z """ 2025-12-04T07:56:47.5443338Z gh = get_gh_client(github_token) 2025-12-04T07:56:47.5444332Z issue = get_issue(gh, repo, issue_num) 2025-12-04T07:56:47.5445541Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T07:56:47.5446342Z 2025-12-04T07:56:47.5446350Z 2025-12-04T07:56:47.5447085Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T07:56:47.5448472Z for _ in range(num_retries): 2025-12-04T07:56:47.5449337Z try: 2025-12-04T07:56:47.5450095Z req = Request(url=url, headers=headers) 2025-12-04T07:56:47.5451299Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T07:56:47.5452471Z return json.loads(content) 2025-12-04T07:56:47.5453431Z except Exception as e: 2025-12-04T07:56:47.5454416Z log.warning(f"Could not download {url}: {e}") 2025-12-04T07:56:47.5455200Z 2025-12-04T07:56:47.5455894Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T07:56:47.5457211Z return {} 2025-12-04T07:56:47.5457614Z 2025-12-04T07:56:47.5457622Z 2025-12-04T07:56:47.5457901Z @cache 2025-12-04T07:56:47.5459012Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T07:56:47.5460379Z """ 2025-12-04T07:56:47.5461113Z Dynamically get PR information 2025-12-04T07:56:47.5462067Z """ 2025-12-04T07:56:47.5462967Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T07:56:47.5464110Z headers = { 2025-12-04T07:56:47.5464986Z "Accept": "application/vnd.github.v3+json", 2025-12-04T07:56:47.5466090Z "Authorization": f"token {github_token}", 2025-12-04T07:56:47.5467056Z } 2025-12-04T07:56:47.5467825Z json_response: dict[str, Any] = download_json( 2025-12-04T07:56:47.5468911Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T07:56:47.5469901Z headers=headers, 2025-12-04T07:56:47.5470666Z ) 2025-12-04T07:56:47.5471031Z 2025-12-04T07:56:47.5471350Z if not json_response: 2025-12-04T07:56:47.5472373Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T07:56:47.5473478Z return {} 2025-12-04T07:56:47.5473905Z 2025-12-04T07:56:47.5474229Z return json_response 2025-12-04T07:56:47.5474790Z 2025-12-04T07:56:47.5474799Z 2025-12-04T07:56:47.5475508Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T07:56:47.5476840Z """ 2025-12-04T07:56:47.5477779Z Dynamically get the latest list of labels from the pull request 2025-12-04T07:56:47.5478956Z """ 2025-12-04T07:56:47.5479821Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T07:56:47.5480915Z return { 2025-12-04T07:56:47.5481964Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T07:56:47.5483241Z } 2025-12-04T07:56:47.5483610Z 2025-12-04T07:56:47.5483618Z 2025-12-04T07:56:47.5483917Z def main() -> None: 2025-12-04T07:56:47.5484712Z args = parse_args() 2025-12-04T07:56:47.5485199Z 2025-12-04T07:56:47.5485598Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T07:56:47.5486321Z 2025-12-04T07:56:47.5486671Z # Check if the PR is opt-out 2025-12-04T07:56:47.5487550Z if args.pr_number: 2025-12-04T07:56:47.5488724Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T07:56:47.5490173Z if OPT_OUT_LABEL in labels: 2025-12-04T07:56:47.5491070Z log.info( 2025-12-04T07:56:47.5492300Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T07:56:47.5493694Z ) 2025-12-04T07:56:47.5494736Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:47.5495947Z sys.exit() 2025-12-04T07:56:47.5496416Z 2025-12-04T07:56:47.5496711Z try: 2025-12-04T07:56:47.5497493Z rollout_state = get_rollout_state_from_issue( 2025-12-04T07:56:47.5498785Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T07:56:47.5499936Z ) 2025-12-04T07:56:47.5500311Z 2025-12-04T07:56:47.5500681Z username = get_potential_pr_author( 2025-12-04T07:56:47.5501650Z args.github_token, 2025-12-04T07:56:47.5502518Z args.github_repo, 2025-12-04T07:56:47.5503396Z args.github_actor, 2025-12-04T07:56:47.5504288Z args.github_ref_type, 2025-12-04T07:56:47.5505260Z args.github_branch, 2025-12-04T07:56:47.5506086Z ) 2025-12-04T07:56:47.5506458Z 2025-12-04T07:56:47.5506974Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T07:56:47.5507792Z 2025-12-04T07:56:47.5508186Z runner_label_prefix = get_runner_prefix( 2025-12-04T07:56:47.5509198Z rollout_state, 2025-12-04T07:56:47.5510080Z (args.github_issue_owner, username), 2025-12-04T07:56:47.5511080Z args.github_branch, 2025-12-04T07:56:47.5511989Z args.eligible_experiments, 2025-12-04T07:56:47.5512975Z args.opt_out_experiments, 2025-12-04T07:56:47.5513897Z is_canary, 2025-12-04T07:56:47.5514687Z ) 2025-12-04T07:56:47.5515062Z 2025-12-04T07:56:47.5515404Z except Exception as e: 2025-12-04T07:56:47.5516215Z log.error( 2025-12-04T07:56:47.5517432Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T07:56:47.5518929Z ) 2025-12-04T07:56:47.5519312Z 2025-12-04T07:56:47.5519904Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T07:56:47.5520801Z 2025-12-04T07:56:47.5520809Z 2025-12-04T07:56:47.5521140Z if __name__ == "__main__": 2025-12-04T07:56:47.5521935Z main() 2025-12-04T07:56:47.5522332Z 2025-12-04T07:56:47.5621931Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T07:56:47.5623466Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T07:56:47.5644454Z shell: /usr/bin/bash -e {0} 2025-12-04T07:56:47.5645436Z env: 2025-12-04T07:56:47.5646469Z GITHUB_TOKEN: *** 2025-12-04T07:56:47.5647222Z ISSUE_NUMBER: 5132 2025-12-04T07:56:47.5648036Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T07:56:47.5648956Z ISSUE_OWNER: 2025-12-04T07:56:47.5649678Z CHECK_EXPERIMENTS: 2025-12-04T07:56:47.5650477Z OPT_OUT_EXPERIMENTS: 2025-12-04T07:56:47.5651269Z PR_NUMBER: 2025-12-04T07:56:47.5651955Z ##[endgroup] 2025-12-04T07:56:48.5788772Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T07:56:49.2719963Z Collecting urllib3==1.26.18 2025-12-04T07:56:49.3521224Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T07:56:49.3806911Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.1 MB/s eta 0:00:00 2025-12-04T07:56:49.4182558Z Collecting PyGithub==2.3.0 2025-12-04T07:56:49.4377812Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T07:56:49.4955822Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T07:56:49.5139602Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T07:56:49.5180588Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T07:56:49.5202127Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-12-04T07:56:49.5223827Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-12-04T07:56:49.5657699Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T07:56:49.5843202Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T07:56:49.6042360Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-12-04T07:56:49.7537583Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T07:56:49.7727806Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T07:56:49.9316327Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T07:56:49.9509249Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (9.0 kB) 2025-12-04T07:56:49.9843607Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T07:56:50.0030349Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T07:56:50.0416530Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T07:56:50.0641196Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 6.6 MB/s eta 0:00:00 2025-12-04T07:56:50.0839808Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T07:56:50.1065259Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 16.5 MB/s eta 0:00:00 2025-12-04T07:56:50.1249930Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T07:56:50.1533802Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 52.0 MB/s eta 0:00:00 2025-12-04T07:56:50.1720012Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T07:56:50.1922222Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T07:56:50.1963381Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 87.6 MB/s eta 0:00:00 2025-12-04T07:56:50.2148614Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (121 kB) 2025-12-04T07:56:50.2188794Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 47.3 MB/s eta 0:00:00 2025-12-04T07:56:50.2374825Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T07:56:50.2408762Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 56.8 MB/s eta 0:00:00 2025-12-04T07:56:50.5008156Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T07:56:50.9981862Z Successfully installed Deprecated-1.3.1 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.1 urllib3-1.26.18 wrapt-2.0.1 2025-12-04T07:56:51.0840365Z ##[group]Run curr_branch="main" 2025-12-04T07:56:51.0840644Z curr_branch="main" 2025-12-04T07:56:51.0840863Z curr_ref_type="branch" 2025-12-04T07:56:51.0841106Z echo "Current branch is '$curr_branch'" 2025-12-04T07:56:51.0841368Z  2025-12-04T07:56:51.0841547Z python3 runner_determinator.py \ 2025-12-04T07:56:51.0841827Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T07:56:51.0842089Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T07:56:51.0842333Z  --github-branch "$curr_branch" \ 2025-12-04T07:56:51.0842599Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T07:56:51.0842867Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T07:56:51.0843138Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T07:56:51.0843393Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T07:56:51.0843681Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T07:56:51.0844041Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T07:56:51.0844318Z  --pr-number "${PR_NUMBER}" 2025-12-04T07:56:51.0865031Z shell: /usr/bin/bash -e {0} 2025-12-04T07:56:51.0865268Z env: 2025-12-04T07:56:51.0865695Z GITHUB_TOKEN: *** 2025-12-04T07:56:51.0865883Z ISSUE_NUMBER: 5132 2025-12-04T07:56:51.0866098Z TRIGGERING_ACTOR: pytorchmergebot 2025-12-04T07:56:51.0866331Z ISSUE_OWNER: 2025-12-04T07:56:51.0866514Z CHECK_EXPERIMENTS: 2025-12-04T07:56:51.0866717Z OPT_OUT_EXPERIMENTS: 2025-12-04T07:56:51.0866901Z PR_NUMBER: 2025-12-04T07:56:51.0867069Z ##[endgroup] 2025-12-04T07:56:51.0906119Z Current branch is 'main' 2025-12-04T07:56:53.3255452Z INFO : Based on rollout percentage of 75%, enabling experiment lf. 2025-12-04T07:56:53.3256334Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T07:56:53.3256907Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T07:56:53.3257420Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T07:56:53.3257834Z INFO : Setting output: label-type='lf.' 2025-12-04T07:56:53.3595749Z Evaluate and set job outputs 2025-12-04T07:56:53.3602126Z Set output 'label-type' 2025-12-04T07:56:53.3604044Z Cleaning up orphan processes