2025-08-14T20:43:45.9678854Z Current runner version: '2.327.1' 2025-08-14T20:43:45.9711483Z ##[group]Runner Image Provisioner 2025-08-14T20:43:45.9713190Z Hosted Compute Agent 2025-08-14T20:43:45.9714079Z Version: 20250812.370 2025-08-14T20:43:45.9715471Z Commit: 4a2b2bf7520004e3e907c2150c8cabe342a3da32 2025-08-14T20:43:45.9716854Z Build Date: 2025-08-12T16:08:14Z 2025-08-14T20:43:45.9717803Z ##[endgroup] 2025-08-14T20:43:45.9718811Z ##[group]Operating System 2025-08-14T20:43:45.9719735Z Ubuntu 2025-08-14T20:43:45.9720497Z 24.04.2 2025-08-14T20:43:45.9721212Z LTS 2025-08-14T20:43:45.9722077Z ##[endgroup] 2025-08-14T20:43:45.9723118Z ##[group]Runner Image 2025-08-14T20:43:45.9723999Z Image: ubuntu-24.04 2025-08-14T20:43:45.9724968Z Version: 20250804.2.0 2025-08-14T20:43:45.9726701Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250804.2/images/ubuntu/Ubuntu2404-Readme.md 2025-08-14T20:43:45.9729575Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250804.2 2025-08-14T20:43:45.9731249Z ##[endgroup] 2025-08-14T20:43:45.9733390Z ##[group]GITHUB_TOKEN Permissions 2025-08-14T20:43:45.9736386Z Contents: read 2025-08-14T20:43:45.9737329Z Metadata: read 2025-08-14T20:43:45.9738161Z Packages: read 2025-08-14T20:43:45.9739004Z ##[endgroup] 2025-08-14T20:43:45.9742230Z Secret source: Actions 2025-08-14T20:43:45.9744032Z Prepare workflow directory 2025-08-14T20:43:46.0495787Z Prepare all required actions 2025-08-14T20:43:46.0579349Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (1fc683cf17c8c673044538d10266c00f92987be2) 2025-08-14T20:43:46.0587211Z ##[group] Inputs 2025-08-14T20:43:46.0588193Z check_experiments: 2025-08-14T20:43:46.0589068Z opt_out_experiments: 2025-08-14T20:43:46.0590086Z triggering_actor: pytorchmergebot 2025-08-14T20:43:46.0591197Z issue_owner: 2025-08-14T20:43:46.0592057Z curr_branch: main 2025-08-14T20:43:46.0593773Z curr_ref_type: branch 2025-08-14T20:43:46.0594724Z issue_number: 5132 2025-08-14T20:43:46.0595634Z ##[endgroup] 2025-08-14T20:43:46.0596660Z Complete job name: get-label-type / runner-determinator 2025-08-14T20:43:46.1328257Z ##[group]Run cat < runner_determinator.py 2025-08-14T20:43:46.1330496Z cat < runner_determinator.py 2025-08-14T20:43:46.1331151Z # flake8: noqa: G004 2025-08-14T20:43:46.1331643Z  2025-08-14T20:43:46.1332625Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:43:46.1333661Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:43:46.1334548Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:43:46.1335342Z  2025-08-14T20:43:46.1335745Z """ 2025-08-14T20:43:46.1336422Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:43:46.1337424Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:43:46.1338584Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:43:46.1339543Z of which runners should be used to run which job. 2025-08-14T20:43:46.1340214Z  2025-08-14T20:43:46.1340890Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:43:46.1341887Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:43:46.1343248Z settings are considered to be empty with only the second part, the user 2025-08-14T20:43:46.1344049Z list, defined. 2025-08-14T20:43:46.1344605Z  2025-08-14T20:43:46.1345284Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:43:46.1346281Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:43:46.1347259Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:43:46.1348195Z  2025-08-14T20:43:46.1348917Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:43:46.1349938Z The user list is also a comma separated list of additional features or 2025-08-14T20:43:46.1350778Z experiments which the user could be opted in to. 2025-08-14T20:43:46.1351433Z  2025-08-14T20:43:46.1351872Z The user list has the following rules: 2025-08-14T20:43:46.1352851Z  2025-08-14T20:43:46.1353490Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:43:46.1354472Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:43:46.1355430Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:43:46.1356042Z  2025-08-14T20:43:46.1356480Z Example config: 2025-08-14T20:43:46.1357108Z  # A list of experiments that can be opted into. 2025-08-14T20:43:46.1357949Z  # This defines the behavior they'll induce when opted into. 2025-08-14T20:43:46.1358665Z  # Expected syntax is: 2025-08-14T20:43:46.1359479Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:43:46.1360558Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:43:46.1361355Z  2025-08-14T20:43:46.1361845Z  experiments: 2025-08-14T20:43:46.1362476Z  lf: 2025-08-14T20:43:46.1362987Z  rollout_percent: 25 2025-08-14T20:43:46.1363625Z  all_branches: false 2025-08-14T20:43:46.1364163Z  default: true 2025-08-14T20:43:46.1364689Z  --- 2025-08-14T20:43:46.1365144Z  2025-08-14T20:43:46.1365636Z  # Opt-ins: 2025-08-14T20:43:46.1366313Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:43:46.1367493Z  # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:43:46.1368425Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:43:46.1369161Z  # Experiments should be from the above list. 2025-08-14T20:43:46.1369823Z  2025-08-14T20:43:46.1370295Z  @User1,-lf,split_build 2025-08-14T20:43:46.1370869Z  @User2,lf 2025-08-14T20:43:46.1371419Z  @User3,split_build 2025-08-14T20:43:46.1371950Z """ 2025-08-14T20:43:46.1372526Z  2025-08-14T20:43:46.1372936Z import json 2025-08-14T20:43:46.1373559Z import logging 2025-08-14T20:43:46.1374128Z import os 2025-08-14T20:43:46.1374577Z import random 2025-08-14T20:43:46.1375154Z import re 2025-08-14T20:43:46.1375603Z import sys 2025-08-14T20:43:46.1376126Z from argparse import ArgumentParser 2025-08-14T20:43:46.1376856Z from collections.abc import Iterable 2025-08-14T20:43:46.1377500Z from functools import cache 2025-08-14T20:43:46.1378062Z from logging import LogRecord 2025-08-14T20:43:46.1378732Z from typing import Any, NamedTuple 2025-08-14T20:43:46.1379410Z from urllib.request import Request, urlopen 2025-08-14T20:43:46.1380004Z  2025-08-14T20:43:46.1380532Z import yaml 2025-08-14T20:43:46.1381024Z from github import Auth, Github 2025-08-14T20:43:46.1381651Z from github.Issue import Issue 2025-08-14T20:43:46.1382214Z  2025-08-14T20:43:46.1382978Z  2025-08-14T20:43:46.1383548Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:43:46.1384383Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:43:46.1385372Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:43:46.1386321Z  2025-08-14T20:43:46.1386900Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:43:46.1387609Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:43:46.1388254Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:43:46.1388978Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:43:46.1389581Z  2025-08-14T20:43:46.1390077Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:43:46.1457273Z  2025-08-14T20:43:46.1457772Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:43:46.1458316Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:43:46.1458786Z  2025-08-14T20:43:46.1459125Z  2025-08-14T20:43:46.1459504Z class Experiment(NamedTuple): 2025-08-14T20:43:46.1460018Z  rollout_perc: float = ( 2025-08-14T20:43:46.1460734Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:43:46.1461432Z  ) 2025-08-14T20:43:46.1461844Z  all_branches: bool = ( 2025-08-14T20:43:46.1462857Z  False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:43:46.1463547Z  ) 2025-08-14T20:43:46.1463929Z  default: bool = ( 2025-08-14T20:43:46.1464554Z  True # If True, the experiment is enabled by default for all queries 2025-08-14T20:43:46.1465195Z  ) 2025-08-14T20:43:46.1465549Z  2025-08-14T20:43:46.1465926Z  # Add more fields as needed 2025-08-14T20:43:46.1466400Z  2025-08-14T20:43:46.1466740Z  2025-08-14T20:43:46.1467106Z class Settings(NamedTuple): 2025-08-14T20:43:46.1467576Z  """ 2025-08-14T20:43:46.1468071Z  Settings for the experiments that can be opted into. 2025-08-14T20:43:46.1468665Z  """ 2025-08-14T20:43:46.1469019Z  2025-08-14T20:43:46.1469440Z  experiments: dict[str, Experiment] = {} 2025-08-14T20:43:46.1469964Z  2025-08-14T20:43:46.1470495Z  2025-08-14T20:43:46.1470928Z class ColorFormatter(logging.Formatter): 2025-08-14T20:43:46.1471587Z  """Color codes the log messages based on the log level""" 2025-08-14T20:43:46.1472170Z  2025-08-14T20:43:46.1472758Z  COLORS = { 2025-08-14T20:43:46.1473223Z  "WARNING": "\033[33m", # Yellow 2025-08-14T20:43:46.1473756Z  "ERROR": "\033[31m", # Red 2025-08-14T20:43:46.1474280Z  "CRITICAL": "\033[31m", # Red 2025-08-14T20:43:46.1474804Z  "INFO": "\033[0m", # Reset 2025-08-14T20:43:46.1475308Z  "DEBUG": "\033[0m", # Reset 2025-08-14T20:43:46.1475795Z  } 2025-08-14T20:43:46.1476152Z  2025-08-14T20:43:46.1476569Z  def format(self, record: LogRecord) -> str: 2025-08-14T20:43:46.1477325Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:43:46.1478120Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:43:46.1478712Z  return super().format(record) 2025-08-14T20:43:46.1479209Z  2025-08-14T20:43:46.1479552Z  2025-08-14T20:43:46.1479939Z handler = logging.StreamHandler() 2025-08-14T20:43:46.1480687Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:43:46.1481379Z  2025-08-14T20:43:46.1481841Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:43:46.1482560Z log.addHandler(handler) 2025-08-14T20:43:46.1483051Z log.setLevel(logging.INFO) 2025-08-14T20:43:46.1483518Z  2025-08-14T20:43:46.1483844Z  2025-08-14T20:43:46.1484308Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:43:46.1484880Z  """ 2025-08-14T20:43:46.1485417Z  Defines outputs of the github action that invokes this script 2025-08-14T20:43:46.1486218Z  """ 2025-08-14T20:43:46.1486612Z  if not GITHUB_OUTPUT: 2025-08-14T20:43:46.1487707Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:43:46.1488791Z  log.warning( 2025-08-14T20:43:46.1489678Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:43:46.1490576Z  ) 2025-08-14T20:43:46.1491040Z  print(f"::set-output name={key}::{value}") 2025-08-14T20:43:46.1491579Z  return 2025-08-14T20:43:46.1491976Z  2025-08-14T20:43:46.1492491Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:43:46.1493112Z  log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:43:46.1493690Z  f.write(f"{key}={value}\n") 2025-08-14T20:43:46.1494178Z  2025-08-14T20:43:46.1494530Z  2025-08-14T20:43:46.1495046Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:43:46.1495696Z  return frozenset( 2025-08-14T20:43:46.1496347Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:43:46.1497013Z  ) 2025-08-14T20:43:46.1497379Z  2025-08-14T20:43:46.1497715Z  2025-08-14T20:43:46.1498085Z def parse_args() -> Any: 2025-08-14T20:43:46.1498690Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:43:46.1499560Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:43:46.1500318Z  parser.add_argument( 2025-08-14T20:43:46.1500802Z  "--github-issue-repo", 2025-08-14T20:43:46.1501308Z  type=str, 2025-08-14T20:43:46.1501753Z  required=False, 2025-08-14T20:43:46.1502574Z  default="pytorch/test-infra", 2025-08-14T20:43:46.1503250Z  help="GitHub repo to get the issue", 2025-08-14T20:43:46.1503779Z  ) 2025-08-14T20:43:46.1504168Z  parser.add_argument( 2025-08-14T20:43:46.1504652Z  "--github-repo", 2025-08-14T20:43:46.1505111Z  type=str, 2025-08-14T20:43:46.1505541Z  required=True, 2025-08-14T20:43:46.1506056Z  help="GitHub repo where CI is running", 2025-08-14T20:43:46.1506577Z  ) 2025-08-14T20:43:46.1506964Z  parser.add_argument( 2025-08-14T20:43:46.1507603Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:43:46.1508252Z  ) 2025-08-14T20:43:46.1508634Z  parser.add_argument( 2025-08-14T20:43:46.1509300Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:43:46.1509987Z  ) 2025-08-14T20:43:46.1510372Z  parser.add_argument( 2025-08-14T20:43:46.1511046Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:43:46.1511725Z  ) 2025-08-14T20:43:46.1512112Z  parser.add_argument( 2025-08-14T20:43:46.1513142Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:43:46.1513863Z  ) 2025-08-14T20:43:46.1514247Z  parser.add_argument( 2025-08-14T20:43:46.1514732Z  "--github-ref-type", 2025-08-14T20:43:46.1515205Z  type=str, 2025-08-14T20:43:46.1515642Z  required=True, 2025-08-14T20:43:46.1516166Z  help="Current GitHub ref type, branch or tag", 2025-08-14T20:43:46.1516713Z  ) 2025-08-14T20:43:46.1517100Z  parser.add_argument( 2025-08-14T20:43:46.1517739Z  "--eligible-experiments", 2025-08-14T20:43:46.1518290Z  type=_str_comma_separated_to_set, 2025-08-14T20:43:46.1518808Z  required=False, 2025-08-14T20:43:46.1519264Z  default="", 2025-08-14T20:43:46.1520127Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:43:46.1521026Z  ) 2025-08-14T20:43:46.1521417Z  parser.add_argument( 2025-08-14T20:43:46.1521905Z  "--opt-out-experiments", 2025-08-14T20:43:46.1522565Z  type=_str_comma_separated_to_set, 2025-08-14T20:43:46.1523088Z  required=False, 2025-08-14T20:43:46.1523546Z  default="", 2025-08-14T20:43:46.1523974Z  help=( 2025-08-14T20:43:46.1524664Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:43:46.1525781Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:43:46.1526591Z  ), 2025-08-14T20:43:46.1526969Z  ) 2025-08-14T20:43:46.1527347Z  parser.add_argument( 2025-08-14T20:43:46.1527830Z  "--pr-number", 2025-08-14T20:43:46.1528277Z  type=str, 2025-08-14T20:43:46.1528713Z  required=False, 2025-08-14T20:43:46.1529159Z  default="", 2025-08-14T20:43:46.1529685Z  help="the optional PR number where this is run", 2025-08-14T20:43:46.1530247Z  ) 2025-08-14T20:43:46.1530607Z  2025-08-14T20:43:46.1530991Z  return parser.parse_args() 2025-08-14T20:43:46.1531468Z  2025-08-14T20:43:46.1531811Z  2025-08-14T20:43:46.1532538Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:43:46.1533535Z  auth = Auth.Token(github_token) 2025-08-14T20:43:46.1534064Z  return Github(auth=auth) 2025-08-14T20:43:46.1534527Z  2025-08-14T20:43:46.1534861Z  2025-08-14T20:43:46.1535499Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:43:46.1536280Z  repo = gh.get_repo(repo) 2025-08-14T20:43:46.1536806Z  return repo.get_issue(number=issue_num) 2025-08-14T20:43:46.1537322Z  2025-08-14T20:43:46.1537649Z  2025-08-14T20:43:46.1538024Z def get_potential_pr_author( 2025-08-14T20:43:46.1538702Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:43:46.1539365Z ) -> str: 2025-08-14T20:43:46.1539922Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:43:46.1540725Z  # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:43:46.1541483Z  # embedded in the tag name: ciflow// 2025-08-14T20:43:46.1542044Z  2025-08-14T20:43:46.1542671Z  gh = get_gh_client(github_token) 2025-08-14T20:43:46.1543178Z  2025-08-14T20:43:46.1543642Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:43:46.1544273Z  split_tag = ref_name.split("/") 2025-08-14T20:43:46.1544772Z  if ( 2025-08-14T20:43:46.1545189Z  len(split_tag) == 3 2025-08-14T20:43:46.1545699Z  and split_tag[0] == "ciflow" 2025-08-14T20:43:46.1546235Z  and split_tag[2].isnumeric() 2025-08-14T20:43:46.1546721Z  ): 2025-08-14T20:43:46.1547137Z  pr_number = split_tag[2] 2025-08-14T20:43:46.1547630Z  try: 2025-08-14T20:43:46.1548094Z  repository = gh.get_repo(repo) 2025-08-14T20:43:46.1548867Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:43:46.1549478Z  except Exception as e: 2025-08-14T20:43:46.1550016Z  raise Exception( # noqa: TRY002 2025-08-14T20:43:46.1550693Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:43:46.1551327Z  ) from e 2025-08-14T20:43:46.1551901Z  return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:43:46.1553330Z  # In all other cases, return the original input username 2025-08-14T20:43:46.1553937Z  return username 2025-08-14T20:43:46.1554386Z  2025-08-14T20:43:46.1554725Z  2025-08-14T20:43:46.1555143Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:43:46.1555677Z  """ 2025-08-14T20:43:46.1556340Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:43:46.1557106Z  """ 2025-08-14T20:43:46.1557670Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:43:46.1558314Z  2025-08-14T20:43:46.1558655Z  2025-08-14T20:43:46.1559036Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:43:46.1559533Z  try: 2025-08-14T20:43:46.1559947Z  data = yaml.safe_load(yaml_text) 2025-08-14T20:43:46.1560459Z  return data 2025-08-14T20:43:46.1560915Z  except yaml.YAMLError: 2025-08-14T20:43:46.1561431Z  log.exception("Error loading YAML") 2025-08-14T20:43:46.1561945Z  raise 2025-08-14T20:43:46.1562524Z  2025-08-14T20:43:46.1562871Z  2025-08-14T20:43:46.1563474Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:43:46.1564211Z  """ 2025-08-14T20:43:46.1564988Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:43:46.1565716Z  2025-08-14T20:43:46.1566249Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:46.1567000Z  and the text below is the list of opted in users. 2025-08-14T20:43:46.1567554Z  2025-08-14T20:43:46.1568126Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:43:46.1568796Z  """ 2025-08-14T20:43:46.1569263Z  rollout_state_parts = rollout_state.split("---") 2025-08-14T20:43:46.1569861Z  if len(rollout_state_parts) >= 2: 2025-08-14T20:43:46.1570487Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:43:46.1571070Z  else: 2025-08-14T20:43:46.1571637Z  return "", rollout_state 2025-08-14T20:43:46.1572234Z  2025-08-14T20:43:46.1572671Z  2025-08-14T20:43:46.1573077Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:43:46.1573583Z  """ 2025-08-14T20:43:46.1574121Z  Dictionary of users with a list of features they have opted into 2025-08-14T20:43:46.1574740Z  """ 2025-08-14T20:43:46.1575090Z  2025-08-14T20:43:46.1575411Z  2025-08-14T20:43:46.1575933Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:43:46.1576576Z  """ 2025-08-14T20:43:46.1577284Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:43:46.1578085Z  2025-08-14T20:43:46.1578854Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:43:46.1579823Z  - Example line: "@User1,lf,split_build" 2025-08-14T20:43:46.1580636Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:43:46.1581246Z  2025-08-14T20:43:46.1581559Z  2025-08-14T20:43:46.1581878Z  """ 2025-08-14T20:43:46.1582256Z  optins = UserOptins() 2025-08-14T20:43:46.1583080Z  for user in user_optin_text.split("\n"): 2025-08-14T20:43:46.1583640Z  user = user.strip("\r\n\t -") 2025-08-14T20:43:46.1584195Z  if not user or not user.startswith("@"): 2025-08-14T20:43:46.1584748Z  # Not a valid user. Skip 2025-08-14T20:43:46.1585234Z  continue 2025-08-14T20:43:46.1585645Z  2025-08-14T20:43:46.1585990Z  if user: 2025-08-14T20:43:46.1586461Z  usr_name = user.split(",")[0].strip("@") 2025-08-14T20:43:46.1587149Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:43:46.1587778Z  2025-08-14T20:43:46.1588130Z  return optins 2025-08-14T20:43:46.1588541Z  2025-08-14T20:43:46.1588867Z  2025-08-14T20:43:46.1589352Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:43:46.1589949Z  """ 2025-08-14T20:43:46.1590367Z  Check if the experiment name is valid. 2025-08-14T20:43:46.1590879Z  A valid name: 2025-08-14T20:43:46.1591544Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:43:46.1592667Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:43:46.1593368Z  - Cannot contain spaces 2025-08-14T20:43:46.1593840Z  """ 2025-08-14T20:43:46.1594186Z  2025-08-14T20:43:46.1594662Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:43:46.1595370Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:43:46.1596091Z  2025-08-14T20:43:46.1596433Z  if valid: 2025-08-14T20:43:46.1596833Z  return True 2025-08-14T20:43:46.1597238Z  2025-08-14T20:43:46.1597584Z  log.error( 2025-08-14T20:43:46.1598974Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-14T20:43:46.1600424Z  ) 2025-08-14T20:43:46.1600795Z  return False 2025-08-14T20:43:46.1601189Z  2025-08-14T20:43:46.1601514Z  2025-08-14T20:43:46.1602016Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:43:46.1602761Z  """ 2025-08-14T20:43:46.1603362Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:43:46.1604074Z  """ 2025-08-14T20:43:46.1604439Z  try: 2025-08-14T20:43:46.1604824Z  if settings_text: 2025-08-14T20:43:46.1605563Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:43:46.1606316Z  # for easy reading 2025-08-14T20:43:46.1607118Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:43:46.1608203Z  # the backtick character in shell commands. 2025-08-14T20:43:46.1608804Z  backtick = chr(96) # backtick character 2025-08-14T20:43:46.1609477Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:43:46.1610134Z  settings = load_yaml(settings_text) 2025-08-14T20:43:46.1610636Z  2025-08-14T20:43:46.1611423Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:43:46.1612136Z  experiments = {} 2025-08-14T20:43:46.1612697Z  2025-08-14T20:43:46.1613244Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:43:46.1613991Z  if not is_valid_experiment_name(exp_name): 2025-08-14T20:43:46.1615050Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-14T20:43:46.1616041Z  continue 2025-08-14T20:43:46.1616504Z  2025-08-14T20:43:46.1616865Z  valid_settings = {} 2025-08-14T20:43:46.1617397Z  for setting in exp_settings: 2025-08-14T20:43:46.1617969Z  if setting not in Experiment._fields: 2025-08-14T20:43:46.1618530Z  log.warning( 2025-08-14T20:43:46.1619251Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:43:46.1619936Z  ) 2025-08-14T20:43:46.1620383Z  else: 2025-08-14T20:43:46.1620925Z  valid_settings[setting] = exp_settings[setting] 2025-08-14T20:43:46.1621489Z  2025-08-14T20:43:46.1621949Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:43:46.1622859Z  return Settings(experiments) 2025-08-14T20:43:46.1623360Z  2025-08-14T20:43:46.1623710Z  except Exception: 2025-08-14T20:43:46.1624236Z  log.exception("Failed to parse settings") 2025-08-14T20:43:46.1624780Z  2025-08-14T20:43:46.1625138Z  return Settings() 2025-08-14T20:43:46.1625563Z  2025-08-14T20:43:46.1625901Z  2025-08-14T20:43:46.1626482Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:43:46.1627060Z  """ 2025-08-14T20:43:46.1627523Z  Parse settings, if any, from the rollout state. 2025-08-14T20:43:46.1628059Z  2025-08-14T20:43:46.1628594Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:46.1629350Z  and the text below is the list of opted in users. 2025-08-14T20:43:46.1629887Z  2025-08-14T20:43:46.1630468Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:43:46.1631163Z  """ 2025-08-14T20:43:46.1631729Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:46.1632754Z  return parse_settings_from_text(settings_text) 2025-08-14T20:43:46.1633295Z  2025-08-14T20:43:46.1633621Z  2025-08-14T20:43:46.1634077Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:43:46.1634628Z  """ 2025-08-14T20:43:46.1635044Z  Parse users from the rollout state. 2025-08-14T20:43:46.1635543Z  2025-08-14T20:43:46.1635864Z  """ 2025-08-14T20:43:46.1636439Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:46.1637182Z  return parse_user_opt_in_from_text(users_text) 2025-08-14T20:43:46.1637708Z  2025-08-14T20:43:46.1638028Z  2025-08-14T20:43:46.1638635Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:46.1639356Z  """ 2025-08-14T20:43:46.1639794Z  Check if a user is opted into an experiment 2025-08-14T20:43:46.1640341Z  """ 2025-08-14T20:43:46.1640815Z  return experiment_name in user_optins.get(user, []) 2025-08-14T20:43:46.1641516Z  2025-08-14T20:43:46.1641841Z  2025-08-14T20:43:46.1642585Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:46.1643308Z  """ 2025-08-14T20:43:46.1643789Z  Check if a user explicitly opted out of an experiment 2025-08-14T20:43:46.1644358Z  """ 2025-08-14T20:43:46.1644881Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:43:46.1645580Z  experiment_optout = "-" + experiment_name 2025-08-14T20:43:46.1646220Z  if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:43:46.1646811Z  return False 2025-08-14T20:43:46.1647220Z  2025-08-14T20:43:46.1647683Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:43:46.1648274Z  log.warning( 2025-08-14T20:43:46.1649080Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:43:46.1649920Z  ) 2025-08-14T20:43:46.1650280Z  2025-08-14T20:43:46.1650624Z  return True 2025-08-14T20:43:46.1651019Z  2025-08-14T20:43:46.1651349Z  2025-08-14T20:43:46.1651703Z def get_runner_prefix( 2025-08-14T20:43:46.1652170Z  rollout_state: str, 2025-08-14T20:43:46.1652767Z  workflow_requestors: Iterable[str], 2025-08-14T20:43:46.1653272Z  branch: str, 2025-08-14T20:43:46.1653806Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:46.1654501Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:46.1655077Z  is_canary: bool = False, 2025-08-14T20:43:46.1655534Z ) -> str: 2025-08-14T20:43:46.1655974Z  settings = parse_settings(rollout_state) 2025-08-14T20:43:46.1656556Z  user_optins = parse_users(rollout_state) 2025-08-14T20:43:46.1657064Z  2025-08-14T20:43:46.1657542Z  fleet_prefix = "" 2025-08-14T20:43:46.1657987Z  prefixes = [] 2025-08-14T20:43:46.1658653Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:43:46.1659568Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:43:46.1660270Z  log.info( 2025-08-14T20:43:46.1660964Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:43:46.1661682Z  ) 2025-08-14T20:43:46.1662081Z  continue 2025-08-14T20:43:46.1662756Z  2025-08-14T20:43:46.1663138Z  if opt_out_experiments: 2025-08-14T20:43:46.1663699Z  if experiment_name in opt_out_experiments: 2025-08-14T20:43:46.1664334Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:43:46.1664915Z  log.info( 2025-08-14T20:43:46.1665829Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:43:46.1666751Z  ) 2025-08-14T20:43:46.1667159Z  continue 2025-08-14T20:43:46.1667587Z  2025-08-14T20:43:46.1667951Z  if eligible_experiments: 2025-08-14T20:43:46.1668525Z  if experiment_name not in eligible_experiments: 2025-08-14T20:43:46.1669164Z  exp_list = ", ".join(eligible_experiments) 2025-08-14T20:43:46.1669701Z  log.info( 2025-08-14T20:43:46.1670484Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:43:46.1671269Z  ) 2025-08-14T20:43:46.1671832Z  continue 2025-08-14T20:43:46.1672564Z  elif not experiment_settings.default: 2025-08-14T20:43:46.1673096Z  log.info( 2025-08-14T20:43:46.1673770Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:43:46.1674472Z  ) 2025-08-14T20:43:46.1674869Z  continue 2025-08-14T20:43:46.1675272Z  2025-08-14T20:43:46.1675737Z  # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:43:46.1676353Z  opted_out_users = [ 2025-08-14T20:43:46.1676825Z  requestor 2025-08-14T20:43:46.1677316Z  for requestor in workflow_requestors 2025-08-14T20:43:46.1677993Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:43:46.1678607Z  ] 2025-08-14T20:43:46.1678972Z  2025-08-14T20:43:46.1679329Z  if opted_out_users: 2025-08-14T20:43:46.1679798Z  log.info( 2025-08-14T20:43:46.1680442Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:43:46.1681116Z  ) 2025-08-14T20:43:46.1681520Z  continue 2025-08-14T20:43:46.1681933Z  2025-08-14T20:43:46.1682492Z  # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:43:46.1683098Z  opted_in_users = [ 2025-08-14T20:43:46.1683552Z  requestor 2025-08-14T20:43:46.1684043Z  for requestor in workflow_requestors 2025-08-14T20:43:46.1684707Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:43:46.1685306Z  ] 2025-08-14T20:43:46.1685672Z  2025-08-14T20:43:46.1686015Z  enabled = False 2025-08-14T20:43:46.1686478Z  if opted_in_users: 2025-08-14T20:43:46.1687066Z  log.info( 2025-08-14T20:43:46.1687704Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:43:46.1688355Z  ) 2025-08-14T20:43:46.1688768Z  enabled = True 2025-08-14T20:43:46.1689213Z  2025-08-14T20:43:46.1689617Z  elif experiment_settings.rollout_perc: 2025-08-14T20:43:46.1690429Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:43:46.1691326Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:43:46.1691954Z  log.info( 2025-08-14T20:43:46.1693014Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:43:46.1693895Z  ) 2025-08-14T20:43:46.1694327Z  enabled = True 2025-08-14T20:43:46.1694778Z  2025-08-14T20:43:46.1695124Z  if enabled: 2025-08-14T20:43:46.1695585Z  label = experiment_name 2025-08-14T20:43:46.1696146Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:43:46.1696952Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:43:46.1697794Z  # - If it's enabled, then we always list it's prefix first 2025-08-14T20:43:46.1698539Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:43:46.1699177Z  if is_canary: 2025-08-14T20:43:46.1699695Z  label += CANARY_FLEET_SUFFIX 2025-08-14T20:43:46.1700237Z  fleet_prefix = label 2025-08-14T20:43:46.1700854Z  else: 2025-08-14T20:43:46.1701319Z  prefixes.append(label) 2025-08-14T20:43:46.1701803Z  2025-08-14T20:43:46.1702157Z  if len(prefixes) > 1: 2025-08-14T20:43:46.1702893Z  log.error( 2025-08-14T20:43:46.1703908Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-14T20:43:46.1704953Z  ) 2025-08-14T20:43:46.1705347Z  prefixes = prefixes[:1] 2025-08-14T20:43:46.1705820Z  2025-08-14T20:43:46.1706174Z  # Fleet always comes first 2025-08-14T20:43:46.1706655Z  if fleet_prefix: 2025-08-14T20:43:46.1707126Z  prefixes.insert(0, fleet_prefix) 2025-08-14T20:43:46.1707628Z  2025-08-14T20:43:46.1708072Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:43:46.1708631Z  2025-08-14T20:43:46.1708962Z  2025-08-14T20:43:46.1709583Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:43:46.1710327Z  """ 2025-08-14T20:43:46.1710920Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:43:46.1711634Z  2025-08-14T20:43:46.1712195Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:43:46.1713120Z  """ 2025-08-14T20:43:46.1713531Z  gh = get_gh_client(github_token) 2025-08-14T20:43:46.1714077Z  issue = get_issue(gh, repo, issue_num) 2025-08-14T20:43:46.1714716Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:43:46.1715287Z  2025-08-14T20:43:46.1715616Z  2025-08-14T20:43:46.1716189Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:43:46.1717067Z  for _ in range(num_retries): 2025-08-14T20:43:46.1717557Z  try: 2025-08-14T20:43:46.1718001Z  req = Request(url=url, headers=headers) 2025-08-14T20:43:46.1718652Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:43:46.1719281Z  return json.loads(content) 2025-08-14T20:43:46.1719804Z  except Exception as e: 2025-08-14T20:43:46.1720358Z  log.warning(f"Could not download {url}: {e}") 2025-08-14T20:43:46.1720896Z  2025-08-14T20:43:46.1721460Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:43:46.1722148Z  return {} 2025-08-14T20:43:46.1722679Z  2025-08-14T20:43:46.1723248Z  2025-08-14T20:43:46.1723689Z @cache 2025-08-14T20:43:46.1724725Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:43:46.1725578Z  """ 2025-08-14T20:43:46.1725986Z  Dynamically get PR information 2025-08-14T20:43:46.1726472Z  """ 2025-08-14T20:43:46.1726988Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:43:46.1727597Z  headers = { 2025-08-14T20:43:46.1728091Z  "Accept": "application/vnd.github.v3+json", 2025-08-14T20:43:46.1728714Z  "Authorization": f"token {github_token}", 2025-08-14T20:43:46.1729230Z  } 2025-08-14T20:43:46.1729693Z  json_response: dict[str, Any] = download_json( 2025-08-14T20:43:46.1730304Z  url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:43:46.1730859Z  headers=headers, 2025-08-14T20:43:46.1731326Z  ) 2025-08-14T20:43:46.1731680Z  2025-08-14T20:43:46.1732029Z  if not json_response: 2025-08-14T20:43:46.1732972Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:43:46.1733607Z  return {} 2025-08-14T20:43:46.1734016Z  2025-08-14T20:43:46.1734371Z  return json_response 2025-08-14T20:43:46.1734834Z  2025-08-14T20:43:46.1735173Z  2025-08-14T20:43:46.1735762Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:43:46.1736483Z  """ 2025-08-14T20:43:46.1737018Z  Dynamically get the latest list of labels from the pull request 2025-08-14T20:43:46.1737644Z  """ 2025-08-14T20:43:46.1738136Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:43:46.1738730Z  return { 2025-08-14T20:43:46.1739336Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:43:46.1740003Z  } 2025-08-14T20:43:46.1740354Z  2025-08-14T20:43:46.1740678Z  2025-08-14T20:43:46.1741036Z def main() -> None: 2025-08-14T20:43:46.1741474Z  args = parse_args() 2025-08-14T20:43:46.1741903Z  2025-08-14T20:43:46.1742514Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:43:46.1743119Z  2025-08-14T20:43:46.1743482Z  # Check if the PR is opt-out 2025-08-14T20:43:46.1743982Z  if args.pr_number: 2025-08-14T20:43:46.1744656Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:43:46.1745377Z  if OPT_OUT_LABEL in labels: 2025-08-14T20:43:46.1745897Z  log.info( 2025-08-14T20:43:46.1746622Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:43:46.1747365Z  ) 2025-08-14T20:43:46.1747933Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:46.1748595Z  sys.exit() 2025-08-14T20:43:46.1749157Z  2025-08-14T20:43:46.1749495Z  try: 2025-08-14T20:43:46.1749949Z  rollout_state = get_rollout_state_from_issue( 2025-08-14T20:43:46.1750643Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:43:46.1751290Z  ) 2025-08-14T20:43:46.1751654Z  2025-08-14T20:43:46.1752047Z  username = get_potential_pr_author( 2025-08-14T20:43:46.1752797Z  args.github_token, 2025-08-14T20:43:46.1753286Z  args.github_repo, 2025-08-14T20:43:46.1753772Z  args.github_actor, 2025-08-14T20:43:46.1754262Z  args.github_ref_type, 2025-08-14T20:43:46.1754799Z  args.github_branch, 2025-08-14T20:43:46.1755261Z  ) 2025-08-14T20:43:46.1755628Z  2025-08-14T20:43:46.1756113Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:43:46.1756701Z  2025-08-14T20:43:46.1757111Z  runner_label_prefix = get_runner_prefix( 2025-08-14T20:43:46.1757650Z  rollout_state, 2025-08-14T20:43:46.1758191Z  (args.github_issue_owner, username), 2025-08-14T20:43:46.1758723Z  args.github_branch, 2025-08-14T20:43:46.1759242Z  args.eligible_experiments, 2025-08-14T20:43:46.1759778Z  args.opt_out_experiments, 2025-08-14T20:43:46.1760281Z  is_canary, 2025-08-14T20:43:46.1760709Z  ) 2025-08-14T20:43:46.1761070Z  2025-08-14T20:43:46.1761438Z  except Exception as e: 2025-08-14T20:43:46.1761895Z  log.error( 2025-08-14T20:43:46.1762698Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:43:46.1763558Z  ) 2025-08-14T20:43:46.1763933Z  2025-08-14T20:43:46.1764449Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:46.1765076Z  2025-08-14T20:43:46.1765400Z  2025-08-14T20:43:46.1765745Z if __name__ == "__main__": 2025-08-14T20:43:46.1766209Z  main() 2025-08-14T20:43:46.1766579Z  2025-08-14T20:43:46.1766909Z EOF 2025-08-14T20:43:46.1767246Z  2025-08-14T20:43:46.1767612Z cat runner_determinator.py 2025-08-14T20:43:46.2165584Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:46.2166328Z env: 2025-08-14T20:43:46.2166918Z GITHUB_TOKEN: *** 2025-08-14T20:43:46.2167311Z ISSUE_NUMBER: 5132 2025-08-14T20:43:46.2167726Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:46.2168308Z ISSUE_OWNER: 2025-08-14T20:43:46.2168693Z CHECK_EXPERIMENTS: 2025-08-14T20:43:46.2169094Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:43:46.2169500Z PR_NUMBER: 2025-08-14T20:43:46.2169860Z ##[endgroup] 2025-08-14T20:43:46.2400960Z # flake8: noqa: G004 2025-08-14T20:43:46.2401306Z 2025-08-14T20:43:46.2401714Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:43:46.2402974Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:43:46.2403785Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:43:46.2404226Z 2025-08-14T20:43:46.2404374Z """ 2025-08-14T20:43:46.2404924Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:43:46.2405775Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:43:46.2406644Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:43:46.2407452Z of which runners should be used to run which job. 2025-08-14T20:43:46.2407856Z 2025-08-14T20:43:46.2408220Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:43:46.2409295Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:43:46.2410144Z settings are considered to be empty with only the second part, the user 2025-08-14T20:43:46.2410818Z list, defined. 2025-08-14T20:43:46.2411055Z 2025-08-14T20:43:46.2411394Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:43:46.2412259Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:43:46.2413296Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:43:46.2413731Z 2025-08-14T20:43:46.2414078Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:43:46.2414921Z The user list is also a comma separated list of additional features or 2025-08-14T20:43:46.2415638Z experiments which the user could be opted in to. 2025-08-14T20:43:46.2416036Z 2025-08-14T20:43:46.2416218Z The user list has the following rules: 2025-08-14T20:43:46.2416587Z 2025-08-14T20:43:46.2416882Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:43:46.2417694Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:43:46.2418423Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:43:46.2418815Z 2025-08-14T20:43:46.2418976Z Example config: 2025-08-14T20:43:46.2419412Z # A list of experiments that can be opted into. 2025-08-14T20:43:46.2420057Z # This defines the behavior they'll induce when opted into. 2025-08-14T20:43:46.2420653Z # Expected syntax is: 2025-08-14T20:43:46.2421274Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:43:46.2422206Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:43:46.2422986Z 2025-08-14T20:43:46.2423148Z experiments: 2025-08-14T20:43:46.2423532Z lf: 2025-08-14T20:43:46.2423893Z rollout_percent: 25 2025-08-14T20:43:46.2424498Z all_branches: false 2025-08-14T20:43:46.2424942Z default: true 2025-08-14T20:43:46.2425347Z --- 2025-08-14T20:43:46.2425557Z 2025-08-14T20:43:46.2425707Z # Opt-ins: 2025-08-14T20:43:46.2426263Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:43:46.2427083Z # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:43:46.2427829Z # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:43:46.2428463Z # Experiments should be from the above list. 2025-08-14T20:43:46.2428844Z 2025-08-14T20:43:46.2429012Z @User1,-lf,split_build 2025-08-14T20:43:46.2429437Z @User2,lf 2025-08-14T20:43:46.2429805Z @User3,split_build 2025-08-14T20:43:46.2430203Z """ 2025-08-14T20:43:46.2430397Z 2025-08-14T20:43:46.2430547Z import json 2025-08-14T20:43:46.2430905Z import logging 2025-08-14T20:43:46.2431272Z import os 2025-08-14T20:43:46.2431621Z import random 2025-08-14T20:43:46.2432003Z import re 2025-08-14T20:43:46.2432562Z import sys 2025-08-14T20:43:46.2433024Z from argparse import ArgumentParser 2025-08-14T20:43:46.2433538Z from collections.abc import Iterable 2025-08-14T20:43:46.2434048Z from functools import cache 2025-08-14T20:43:46.2434504Z from logging import LogRecord 2025-08-14T20:43:46.2434985Z from typing import Any, NamedTuple 2025-08-14T20:43:46.2435497Z from urllib.request import Request, urlopen 2025-08-14T20:43:46.2435877Z 2025-08-14T20:43:46.2436026Z import yaml 2025-08-14T20:43:46.2436404Z from github import Auth, Github 2025-08-14T20:43:46.2436885Z from github.Issue import Issue 2025-08-14T20:43:46.2437191Z 2025-08-14T20:43:46.2437197Z 2025-08-14T20:43:46.2437402Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:43:46.2438057Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:43:46.2438883Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:43:46.2439423Z 2025-08-14T20:43:46.2439640Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:43:46.2440331Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:43:46.2440829Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:43:46.2441381Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:43:46.2441739Z 2025-08-14T20:43:46.2441926Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:43:46.2442249Z 2025-08-14T20:43:46.2442752Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:43:46.2443224Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:43:46.2443512Z 2025-08-14T20:43:46.2443518Z 2025-08-14T20:43:46.2443688Z class Experiment(NamedTuple): 2025-08-14T20:43:46.2444159Z rollout_perc: float = ( 2025-08-14T20:43:46.2444814Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:43:46.2445516Z ) 2025-08-14T20:43:46.2445885Z all_branches: bool = ( 2025-08-14T20:43:46.2446489Z False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:43:46.2447149Z ) 2025-08-14T20:43:46.2447497Z default: bool = ( 2025-08-14T20:43:46.2448057Z True # If True, the experiment is enabled by default for all queries 2025-08-14T20:43:46.2448669Z ) 2025-08-14T20:43:46.2448871Z 2025-08-14T20:43:46.2449037Z # Add more fields as needed 2025-08-14T20:43:46.2449339Z 2025-08-14T20:43:46.2449345Z 2025-08-14T20:43:46.2449523Z class Settings(NamedTuple): 2025-08-14T20:43:46.2449953Z """ 2025-08-14T20:43:46.2450396Z Settings for the experiments that can be opted into. 2025-08-14T20:43:46.2450951Z """ 2025-08-14T20:43:46.2451154Z 2025-08-14T20:43:46.2451355Z experiments: dict[str, Experiment] = {} 2025-08-14T20:43:46.2451718Z 2025-08-14T20:43:46.2451724Z 2025-08-14T20:43:46.2451918Z class ColorFormatter(logging.Formatter): 2025-08-14T20:43:46.2452703Z """Color codes the log messages based on the log level""" 2025-08-14T20:43:46.2453141Z 2025-08-14T20:43:46.2453300Z COLORS = { 2025-08-14T20:43:46.2453689Z "WARNING": "\033[33m", # Yellow 2025-08-14T20:43:46.2454336Z "ERROR": "\033[31m", # Red 2025-08-14T20:43:46.2454831Z "CRITICAL": "\033[31m", # Red 2025-08-14T20:43:46.2455324Z "INFO": "\033[0m", # Reset 2025-08-14T20:43:46.2455792Z "DEBUG": "\033[0m", # Reset 2025-08-14T20:43:46.2456254Z } 2025-08-14T20:43:46.2456451Z 2025-08-14T20:43:46.2456654Z def format(self, record: LogRecord) -> str: 2025-08-14T20:43:46.2457375Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:43:46.2458118Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:43:46.2458674Z return super().format(record) 2025-08-14T20:43:46.2459005Z 2025-08-14T20:43:46.2459011Z 2025-08-14T20:43:46.2459201Z handler = logging.StreamHandler() 2025-08-14T20:43:46.2459877Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:43:46.2460424Z 2025-08-14T20:43:46.2460649Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:43:46.2461216Z log.addHandler(handler) 2025-08-14T20:43:46.2461649Z log.setLevel(logging.INFO) 2025-08-14T20:43:46.2461932Z 2025-08-14T20:43:46.2461939Z 2025-08-14T20:43:46.2462171Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:43:46.2462897Z """ 2025-08-14T20:43:46.2463399Z Defines outputs of the github action that invokes this script 2025-08-14T20:43:46.2464004Z """ 2025-08-14T20:43:46.2464366Z if not GITHUB_OUTPUT: 2025-08-14T20:43:46.2465380Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:43:46.2466437Z log.warning( 2025-08-14T20:43:46.2467237Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:43:46.2468104Z ) 2025-08-14T20:43:46.2477995Z print(f"::set-output name={key}::{value}") 2025-08-14T20:43:46.2478576Z return 2025-08-14T20:43:46.2478828Z 2025-08-14T20:43:46.2479194Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:43:46.2479770Z log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:43:46.2480325Z f.write(f"{key}={value}\n") 2025-08-14T20:43:46.2480655Z 2025-08-14T20:43:46.2480662Z 2025-08-14T20:43:46.2480947Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:43:46.2481555Z return frozenset( 2025-08-14T20:43:46.2482151Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:43:46.2483096Z ) 2025-08-14T20:43:46.2483305Z 2025-08-14T20:43:46.2483311Z 2025-08-14T20:43:46.2483474Z def parse_args() -> Any: 2025-08-14T20:43:46.2483994Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:43:46.2484809Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:43:46.2485553Z parser.add_argument( 2025-08-14T20:43:46.2485990Z "--github-issue-repo", 2025-08-14T20:43:46.2486440Z type=str, 2025-08-14T20:43:46.2486826Z required=False, 2025-08-14T20:43:46.2487267Z default="pytorch/test-infra", 2025-08-14T20:43:46.2487783Z help="GitHub repo to get the issue", 2025-08-14T20:43:46.2488280Z ) 2025-08-14T20:43:46.2488627Z parser.add_argument( 2025-08-14T20:43:46.2489072Z "--github-repo", 2025-08-14T20:43:46.2489485Z type=str, 2025-08-14T20:43:46.2489875Z required=True, 2025-08-14T20:43:46.2490311Z help="GitHub repo where CI is running", 2025-08-14T20:43:46.2490822Z ) 2025-08-14T20:43:46.2491179Z parser.add_argument( 2025-08-14T20:43:46.2491756Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:43:46.2492707Z ) 2025-08-14T20:43:46.2493073Z parser.add_argument( 2025-08-14T20:43:46.2493671Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:43:46.2494325Z ) 2025-08-14T20:43:46.2494852Z parser.add_argument( 2025-08-14T20:43:46.2495472Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:43:46.2496139Z ) 2025-08-14T20:43:46.2496499Z parser.add_argument( 2025-08-14T20:43:46.2497097Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:43:46.2497769Z ) 2025-08-14T20:43:46.2498117Z parser.add_argument( 2025-08-14T20:43:46.2498551Z "--github-ref-type", 2025-08-14T20:43:46.2498987Z type=str, 2025-08-14T20:43:46.2499367Z required=True, 2025-08-14T20:43:46.2499826Z help="Current GitHub ref type, branch or tag", 2025-08-14T20:43:46.2500359Z ) 2025-08-14T20:43:46.2500710Z parser.add_argument( 2025-08-14T20:43:46.2501148Z "--eligible-experiments", 2025-08-14T20:43:46.2501638Z type=_str_comma_separated_to_set, 2025-08-14T20:43:46.2502142Z required=False, 2025-08-14T20:43:46.2502785Z default="", 2025-08-14T20:43:46.2503604Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:43:46.2504490Z ) 2025-08-14T20:43:46.2504834Z parser.add_argument( 2025-08-14T20:43:46.2505270Z "--opt-out-experiments", 2025-08-14T20:43:46.2505755Z type=_str_comma_separated_to_set, 2025-08-14T20:43:46.2506252Z required=False, 2025-08-14T20:43:46.2506656Z default="", 2025-08-14T20:43:46.2507032Z help=( 2025-08-14T20:43:46.2507669Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:43:46.2508729Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:43:46.2509524Z ), 2025-08-14T20:43:46.2509858Z ) 2025-08-14T20:43:46.2510212Z parser.add_argument( 2025-08-14T20:43:46.2510627Z "--pr-number", 2025-08-14T20:43:46.2511045Z type=str, 2025-08-14T20:43:46.2511428Z required=False, 2025-08-14T20:43:46.2511836Z default="", 2025-08-14T20:43:46.2512547Z help="the optional PR number where this is run", 2025-08-14T20:43:46.2513107Z ) 2025-08-14T20:43:46.2513304Z 2025-08-14T20:43:46.2513486Z return parser.parse_args() 2025-08-14T20:43:46.2513793Z 2025-08-14T20:43:46.2513799Z 2025-08-14T20:43:46.2514170Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:43:46.2514889Z auth = Auth.Token(github_token) 2025-08-14T20:43:46.2515383Z return Github(auth=auth) 2025-08-14T20:43:46.2563249Z 2025-08-14T20:43:46.2563261Z 2025-08-14T20:43:46.2563803Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:43:46.2564682Z repo = gh.get_repo(repo) 2025-08-14T20:43:46.2565180Z return repo.get_issue(number=issue_num) 2025-08-14T20:43:46.2565536Z 2025-08-14T20:43:46.2565542Z 2025-08-14T20:43:46.2565724Z def get_potential_pr_author( 2025-08-14T20:43:46.2566356Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:43:46.2567003Z ) -> str: 2025-08-14T20:43:46.2567491Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:43:46.2568271Z # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:43:46.2568977Z # embedded in the tag name: ciflow// 2025-08-14T20:43:46.2569399Z 2025-08-14T20:43:46.2569575Z gh = get_gh_client(github_token) 2025-08-14T20:43:46.2569908Z 2025-08-14T20:43:46.2570165Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:43:46.2570764Z split_tag = ref_name.split("/") 2025-08-14T20:43:46.2571267Z if ( 2025-08-14T20:43:46.2571637Z len(split_tag) == 3 2025-08-14T20:43:46.2572105Z and split_tag[0] == "ciflow" 2025-08-14T20:43:46.2572839Z and split_tag[2].isnumeric() 2025-08-14T20:43:46.2573329Z ): 2025-08-14T20:43:46.2573888Z pr_number = split_tag[2] 2025-08-14T20:43:46.2574375Z try: 2025-08-14T20:43:46.2574799Z repository = gh.get_repo(repo) 2025-08-14T20:43:46.2575399Z pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:43:46.2575982Z except Exception as e: 2025-08-14T20:43:46.2576481Z raise Exception( # noqa: TRY002 2025-08-14T20:43:46.2577143Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:43:46.2577760Z ) from e 2025-08-14T20:43:46.2578281Z return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:43:46.2578950Z # In all other cases, return the original input username 2025-08-14T20:43:46.2579522Z return username 2025-08-14T20:43:46.2579766Z 2025-08-14T20:43:46.2579773Z 2025-08-14T20:43:46.2579985Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:43:46.2580497Z """ 2025-08-14T20:43:46.2581110Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:43:46.2581861Z """ 2025-08-14T20:43:46.2582634Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:43:46.2583165Z 2025-08-14T20:43:46.2583173Z 2025-08-14T20:43:46.2583357Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:43:46.2583824Z try: 2025-08-14T20:43:46.2584199Z data = yaml.safe_load(yaml_text) 2025-08-14T20:43:46.2584687Z return data 2025-08-14T20:43:46.2585093Z except yaml.YAMLError: 2025-08-14T20:43:46.2585560Z log.exception("Error loading YAML") 2025-08-14T20:43:46.2586057Z raise 2025-08-14T20:43:46.2586274Z 2025-08-14T20:43:46.2586281Z 2025-08-14T20:43:46.2586658Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:43:46.2587376Z """ 2025-08-14T20:43:46.2587968Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:43:46.2588553Z 2025-08-14T20:43:46.2589017Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:46.2589763Z and the text below is the list of opted in users. 2025-08-14T20:43:46.2590207Z 2025-08-14T20:43:46.2590710Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:43:46.2591413Z """ 2025-08-14T20:43:46.2591846Z rollout_state_parts = rollout_state.split("---") 2025-08-14T20:43:46.2592669Z if len(rollout_state_parts) >= 2: 2025-08-14T20:43:46.2593263Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:43:46.2593833Z else: 2025-08-14T20:43:46.2594204Z return "", rollout_state 2025-08-14T20:43:46.2594511Z 2025-08-14T20:43:46.2594517Z 2025-08-14T20:43:46.2594699Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:43:46.2595198Z """ 2025-08-14T20:43:46.2595684Z Dictionary of users with a list of features they have opted into 2025-08-14T20:43:46.2596310Z """ 2025-08-14T20:43:46.2596517Z 2025-08-14T20:43:46.2596524Z 2025-08-14T20:43:46.2596848Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:43:46.2597469Z """ 2025-08-14T20:43:46.2598136Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:43:46.2598789Z 2025-08-14T20:43:46.2599361Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:43:46.2600316Z - Example line: "@User1,lf,split_build" 2025-08-14T20:43:46.2601020Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:43:46.2601720Z 2025-08-14T20:43:46.2601727Z 2025-08-14T20:43:46.2601882Z """ 2025-08-14T20:43:46.2602244Z optins = UserOptins() 2025-08-14T20:43:46.2602934Z for user in user_optin_text.split("\n"): 2025-08-14T20:43:46.2603475Z user = user.strip("\r\n\t -") 2025-08-14T20:43:46.2604172Z if not user or not user.startswith("@"): 2025-08-14T20:43:46.2604722Z # Not a valid user. Skip 2025-08-14T20:43:46.2605195Z continue 2025-08-14T20:43:46.2605446Z 2025-08-14T20:43:46.2605591Z if user: 2025-08-14T20:43:46.2606015Z usr_name = user.split(",")[0].strip("@") 2025-08-14T20:43:46.2606683Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:43:46.2607167Z 2025-08-14T20:43:46.2607326Z return optins 2025-08-14T20:43:46.2607562Z 2025-08-14T20:43:46.2607568Z 2025-08-14T20:43:46.2607829Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:43:46.2608409Z """ 2025-08-14T20:43:46.2608795Z Check if the experiment name is valid. 2025-08-14T20:43:46.2609307Z A valid name: 2025-08-14T20:43:46.2609930Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:43:46.2610823Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:43:46.2611519Z - Cannot contain spaces 2025-08-14T20:43:46.2611965Z """ 2025-08-14T20:43:46.2612175Z 2025-08-14T20:43:46.2612538Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:43:46.2613219Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:43:46.2613657Z 2025-08-14T20:43:46.2613801Z if valid: 2025-08-14T20:43:46.2614167Z return True 2025-08-14T20:43:46.2614416Z 2025-08-14T20:43:46.2614564Z log.error( 2025-08-14T20:43:46.2615920Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-14T20:43:46.2617412Z ) 2025-08-14T20:43:46.2617749Z return False 2025-08-14T20:43:46.2617984Z 2025-08-14T20:43:46.2617990Z 2025-08-14T20:43:46.2618276Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:43:46.2618876Z """ 2025-08-14T20:43:46.2619559Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:43:46.2620254Z """ 2025-08-14T20:43:46.2620600Z try: 2025-08-14T20:43:46.2620955Z if settings_text: 2025-08-14T20:43:46.2621663Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:43:46.2622691Z # for easy reading 2025-08-14T20:43:46.2623450Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:43:46.2624310Z # the backtick character in shell commands. 2025-08-14T20:43:46.2624891Z backtick = chr(96) # backtick character 2025-08-14T20:43:46.2625539Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:43:46.2626182Z settings = load_yaml(settings_text) 2025-08-14T20:43:46.2626555Z 2025-08-14T20:43:46.2626941Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:43:46.2627681Z experiments = {} 2025-08-14T20:43:46.2627978Z 2025-08-14T20:43:46.2628333Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:43:46.2629058Z if not is_valid_experiment_name(exp_name): 2025-08-14T20:43:46.2630095Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-14T20:43:46.2631075Z continue 2025-08-14T20:43:46.2631360Z 2025-08-14T20:43:46.2631534Z valid_settings = {} 2025-08-14T20:43:46.2632035Z for setting in exp_settings: 2025-08-14T20:43:46.2632822Z if setting not in Experiment._fields: 2025-08-14T20:43:46.2633363Z log.warning( 2025-08-14T20:43:46.2634045Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:43:46.2634884Z ) 2025-08-14T20:43:46.2635305Z else: 2025-08-14T20:43:46.2635800Z valid_settings[setting] = exp_settings[setting] 2025-08-14T20:43:46.2636224Z 2025-08-14T20:43:46.2636479Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:43:46.2637096Z return Settings(experiments) 2025-08-14T20:43:46.2637448Z 2025-08-14T20:43:46.2637608Z except Exception: 2025-08-14T20:43:46.2638075Z log.exception("Failed to parse settings") 2025-08-14T20:43:46.2638452Z 2025-08-14T20:43:46.2638606Z return Settings() 2025-08-14T20:43:46.2638870Z 2025-08-14T20:43:46.2638876Z 2025-08-14T20:43:46.2639103Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:43:46.2639643Z """ 2025-08-14T20:43:46.2640054Z Parse settings, if any, from the rollout state. 2025-08-14T20:43:46.2640454Z 2025-08-14T20:43:46.2640792Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:43:46.2641513Z and the text below is the list of opted in users. 2025-08-14T20:43:46.2641928Z 2025-08-14T20:43:46.2642478Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:43:46.2643226Z """ 2025-08-14T20:43:46.2643762Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:46.2644492Z return parse_settings_from_text(settings_text) 2025-08-14T20:43:46.2644925Z 2025-08-14T20:43:46.2644931Z 2025-08-14T20:43:46.2645160Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:43:46.2645714Z """ 2025-08-14T20:43:46.2646082Z Parse users from the rollout state. 2025-08-14T20:43:46.2646438Z 2025-08-14T20:43:46.2646583Z """ 2025-08-14T20:43:46.2647084Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:43:46.2647801Z return parse_user_opt_in_from_text(users_text) 2025-08-14T20:43:46.2648202Z 2025-08-14T20:43:46.2648209Z 2025-08-14T20:43:46.2648725Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:46.2649447Z """ 2025-08-14T20:43:46.2649854Z Check if a user is opted into an experiment 2025-08-14T20:43:46.2650368Z """ 2025-08-14T20:43:46.2650798Z return experiment_name in user_optins.get(user, []) 2025-08-14T20:43:46.2651199Z 2025-08-14T20:43:46.2651205Z 2025-08-14T20:43:46.2651586Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:43:46.2652478Z """ 2025-08-14T20:43:46.2653000Z Check if a user explicitly opted out of an experiment 2025-08-14T20:43:46.2653564Z """ 2025-08-14T20:43:46.2654049Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:43:46.2654700Z experiment_optout = "-" + experiment_name 2025-08-14T20:43:46.2655325Z if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:43:46.2655910Z return False 2025-08-14T20:43:46.2656163Z 2025-08-14T20:43:46.2656418Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:43:46.2657001Z log.warning( 2025-08-14T20:43:46.2657750Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:43:46.2658587Z ) 2025-08-14T20:43:46.2658789Z 2025-08-14T20:43:46.2658936Z return True 2025-08-14T20:43:46.2659175Z 2025-08-14T20:43:46.2659181Z 2025-08-14T20:43:46.2659338Z def get_runner_prefix( 2025-08-14T20:43:46.2659756Z rollout_state: str, 2025-08-14T20:43:46.2660197Z workflow_requestors: Iterable[str], 2025-08-14T20:43:46.2660697Z branch: str, 2025-08-14T20:43:46.2661164Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:46.2661796Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:43:46.2662596Z is_canary: bool = False, 2025-08-14T20:43:46.2663060Z ) -> str: 2025-08-14T20:43:46.2663615Z settings = parse_settings(rollout_state) 2025-08-14T20:43:46.2664183Z user_optins = parse_users(rollout_state) 2025-08-14T20:43:46.2664555Z 2025-08-14T20:43:46.2664713Z fleet_prefix = "" 2025-08-14T20:43:46.2665127Z prefixes = [] 2025-08-14T20:43:46.2665715Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:43:46.2666603Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:43:46.2667283Z log.info( 2025-08-14T20:43:46.2667913Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:43:46.2668635Z ) 2025-08-14T20:43:46.2668996Z continue 2025-08-14T20:43:46.2669249Z 2025-08-14T20:43:46.2669417Z if opt_out_experiments: 2025-08-14T20:43:46.2669927Z if experiment_name in opt_out_experiments: 2025-08-14T20:43:46.2670538Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:43:46.2671109Z log.info( 2025-08-14T20:43:46.2671973Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:43:46.2673151Z ) 2025-08-14T20:43:46.2673526Z continue 2025-08-14T20:43:46.2673798Z 2025-08-14T20:43:46.2673967Z if eligible_experiments: 2025-08-14T20:43:46.2674499Z if experiment_name not in eligible_experiments: 2025-08-14T20:43:46.2675115Z exp_list = ", ".join(eligible_experiments) 2025-08-14T20:43:46.2675651Z log.info( 2025-08-14T20:43:46.2676389Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:43:46.2677181Z ) 2025-08-14T20:43:46.2677555Z continue 2025-08-14T20:43:46.2678015Z elif not experiment_settings.default: 2025-08-14T20:43:46.2678536Z log.info( 2025-08-14T20:43:46.2679290Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:43:46.2679994Z ) 2025-08-14T20:43:46.2680359Z continue 2025-08-14T20:43:46.2680607Z 2025-08-14T20:43:46.2680872Z # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:43:46.2681461Z opted_out_users = [ 2025-08-14T20:43:46.2681890Z requestor 2025-08-14T20:43:46.2682518Z for requestor in workflow_requestors 2025-08-14T20:43:46.2683193Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:43:46.2683800Z ] 2025-08-14T20:43:46.2684023Z 2025-08-14T20:43:46.2684180Z if opted_out_users: 2025-08-14T20:43:46.2684609Z log.info( 2025-08-14T20:43:46.2685192Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:43:46.2685847Z ) 2025-08-14T20:43:46.2686203Z continue 2025-08-14T20:43:46.2686441Z 2025-08-14T20:43:46.2686691Z # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:43:46.2687265Z opted_in_users = [ 2025-08-14T20:43:46.2687689Z requestor 2025-08-14T20:43:46.2688117Z for requestor in workflow_requestors 2025-08-14T20:43:46.2688747Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:43:46.2689324Z ] 2025-08-14T20:43:46.2689527Z 2025-08-14T20:43:46.2689676Z enabled = False 2025-08-14T20:43:46.2690089Z if opted_in_users: 2025-08-14T20:43:46.2690504Z log.info( 2025-08-14T20:43:46.2691065Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:43:46.2691711Z ) 2025-08-14T20:43:46.2692080Z enabled = True 2025-08-14T20:43:46.2692463Z 2025-08-14T20:43:46.2692662Z elif experiment_settings.rollout_perc: 2025-08-14T20:43:46.2693453Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:43:46.2694480Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:43:46.2695097Z log.info( 2025-08-14T20:43:46.2695904Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:43:46.2696772Z ) 2025-08-14T20:43:46.2697166Z enabled = True 2025-08-14T20:43:46.2697468Z 2025-08-14T20:43:46.2697612Z if enabled: 2025-08-14T20:43:46.2698019Z label = experiment_name 2025-08-14T20:43:46.2698537Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:43:46.2699317Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:43:46.2700163Z # - If it's enabled, then we always list it's prefix first 2025-08-14T20:43:46.2700892Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:43:46.2701520Z if is_canary: 2025-08-14T20:43:46.2701982Z label += CANARY_FLEET_SUFFIX 2025-08-14T20:43:46.2702763Z fleet_prefix = label 2025-08-14T20:43:46.2703237Z else: 2025-08-14T20:43:46.2703644Z prefixes.append(label) 2025-08-14T20:43:46.2703992Z 2025-08-14T20:43:46.2704158Z if len(prefixes) > 1: 2025-08-14T20:43:46.2704576Z log.error( 2025-08-14T20:43:46.2705537Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-14T20:43:46.2706587Z ) 2025-08-14T20:43:46.2706964Z prefixes = prefixes[:1] 2025-08-14T20:43:46.2707272Z 2025-08-14T20:43:46.2707438Z # Fleet always comes first 2025-08-14T20:43:46.2707888Z if fleet_prefix: 2025-08-14T20:43:46.2708320Z prefixes.insert(0, fleet_prefix) 2025-08-14T20:43:46.2708685Z 2025-08-14T20:43:46.2709055Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:43:46.2709463Z 2025-08-14T20:43:46.2709469Z 2025-08-14T20:43:46.2709876Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:43:46.2710604Z """ 2025-08-14T20:43:46.2711156Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:43:46.2711696Z 2025-08-14T20:43:46.2712048Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:43:46.2712959Z """ 2025-08-14T20:43:46.2713332Z gh = get_gh_client(github_token) 2025-08-14T20:43:46.2713844Z issue = get_issue(gh, repo, issue_num) 2025-08-14T20:43:46.2714452Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:43:46.2714893Z 2025-08-14T20:43:46.2714899Z 2025-08-14T20:43:46.2715266Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:43:46.2715997Z for _ in range(num_retries): 2025-08-14T20:43:46.2716486Z try: 2025-08-14T20:43:46.2716906Z req = Request(url=url, headers=headers) 2025-08-14T20:43:46.2717543Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:43:46.2718157Z return json.loads(content) 2025-08-14T20:43:46.2718658Z except Exception as e: 2025-08-14T20:43:46.2719174Z log.warning(f"Could not download {url}: {e}") 2025-08-14T20:43:46.2719578Z 2025-08-14T20:43:46.2719927Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:43:46.2720598Z return {} 2025-08-14T20:43:46.2720826Z 2025-08-14T20:43:46.2720832Z 2025-08-14T20:43:46.2720970Z @cache 2025-08-14T20:43:46.2721536Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:43:46.2722237Z """ 2025-08-14T20:43:46.2722818Z Dynamically get PR information 2025-08-14T20:43:46.2723427Z """ 2025-08-14T20:43:46.2723906Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:43:46.2724497Z headers = { 2025-08-14T20:43:46.2724929Z "Accept": "application/vnd.github.v3+json", 2025-08-14T20:43:46.2725494Z "Authorization": f"token {github_token}", 2025-08-14T20:43:46.2726061Z } 2025-08-14T20:43:46.2726462Z json_response: dict[str, Any] = download_json( 2025-08-14T20:43:46.2727037Z url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:43:46.2727559Z headers=headers, 2025-08-14T20:43:46.2727960Z ) 2025-08-14T20:43:46.2728160Z 2025-08-14T20:43:46.2728329Z if not json_response: 2025-08-14T20:43:46.2728870Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:43:46.2729461Z return {} 2025-08-14T20:43:46.2729697Z 2025-08-14T20:43:46.2729857Z return json_response 2025-08-14T20:43:46.2730138Z 2025-08-14T20:43:46.2730144Z 2025-08-14T20:43:46.2730505Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:43:46.2731198Z """ 2025-08-14T20:43:46.2731690Z Dynamically get the latest list of labels from the pull request 2025-08-14T20:43:46.2732501Z """ 2025-08-14T20:43:46.2732999Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:43:46.2733579Z return { 2025-08-14T20:43:46.2734116Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:43:46.2734788Z } 2025-08-14T20:43:46.2734984Z 2025-08-14T20:43:46.2734990Z 2025-08-14T20:43:46.2735149Z def main() -> None: 2025-08-14T20:43:46.2735542Z args = parse_args() 2025-08-14T20:43:46.2735808Z 2025-08-14T20:43:46.2736009Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:43:46.2736386Z 2025-08-14T20:43:46.2736555Z # Check if the PR is opt-out 2025-08-14T20:43:46.2737025Z if args.pr_number: 2025-08-14T20:43:46.2737633Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:43:46.2738481Z if OPT_OUT_LABEL in labels: 2025-08-14T20:43:46.2738963Z log.info( 2025-08-14T20:43:46.2739607Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:43:46.2740326Z ) 2025-08-14T20:43:46.2740840Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:46.2741475Z sys.exit() 2025-08-14T20:43:46.2741727Z 2025-08-14T20:43:46.2741872Z try: 2025-08-14T20:43:46.2742467Z rollout_state = get_rollout_state_from_issue( 2025-08-14T20:43:46.2743214Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:43:46.2743827Z ) 2025-08-14T20:43:46.2744034Z 2025-08-14T20:43:46.2744224Z username = get_potential_pr_author( 2025-08-14T20:43:46.2744766Z args.github_token, 2025-08-14T20:43:46.2745226Z args.github_repo, 2025-08-14T20:43:46.2745682Z args.github_actor, 2025-08-14T20:43:46.2746154Z args.github_ref_type, 2025-08-14T20:43:46.2746626Z args.github_branch, 2025-08-14T20:43:46.2747060Z ) 2025-08-14T20:43:46.2747262Z 2025-08-14T20:43:46.2747523Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:43:46.2747967Z 2025-08-14T20:43:46.2748162Z runner_label_prefix = get_runner_prefix( 2025-08-14T20:43:46.2748691Z rollout_state, 2025-08-14T20:43:46.2749145Z (args.github_issue_owner, username), 2025-08-14T20:43:46.2749664Z args.github_branch, 2025-08-14T20:43:46.2750127Z args.eligible_experiments, 2025-08-14T20:43:46.2750643Z args.opt_out_experiments, 2025-08-14T20:43:46.2751123Z is_canary, 2025-08-14T20:43:46.2751518Z ) 2025-08-14T20:43:46.2751722Z 2025-08-14T20:43:46.2751887Z except Exception as e: 2025-08-14T20:43:46.2752829Z log.error( 2025-08-14T20:43:46.2753494Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:43:46.2754412Z ) 2025-08-14T20:43:46.2754617Z 2025-08-14T20:43:46.2754920Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:43:46.2755395Z 2025-08-14T20:43:46.2755401Z 2025-08-14T20:43:46.2755561Z if __name__ == "__main__": 2025-08-14T20:43:46.2755981Z main() 2025-08-14T20:43:46.2756188Z 2025-08-14T20:43:46.2842796Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:43:46.2843646Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:43:46.2883714Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:46.2884177Z env: 2025-08-14T20:43:46.2884741Z GITHUB_TOKEN: *** 2025-08-14T20:43:46.2885124Z ISSUE_NUMBER: 5132 2025-08-14T20:43:46.2885533Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:46.2885999Z ISSUE_OWNER: 2025-08-14T20:43:46.2886367Z CHECK_EXPERIMENTS: 2025-08-14T20:43:46.2886758Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:43:46.2887154Z PR_NUMBER: 2025-08-14T20:43:46.2887490Z ##[endgroup] 2025-08-14T20:43:47.2703242Z Defaulting to user installation because normal site-packages is not writeable 2025-08-14T20:43:48.2678719Z Collecting urllib3==1.26.18 2025-08-14T20:43:48.3026552Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-14T20:43:48.3260025Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.2 MB/s eta 0:00:00 2025-08-14T20:43:48.3471463Z Collecting PyGithub==2.3.0 2025-08-14T20:43:48.3502616Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-14T20:43:48.3905944Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-14T20:43:48.3973778Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl.metadata (8.6 kB) 2025-08-14T20:43:48.4015985Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-14T20:43:48.4032742Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-08-14T20:43:48.4047856Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-14T20:43:48.4284239Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-14T20:43:48.4315371Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-14T20:43:48.4538904Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-08-14T20:43:48.5655394Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:43:48.5690336Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-14T20:43:48.6816650Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-14T20:43:48.6855704Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-08-14T20:43:48.7035079Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:43:48.7097401Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-14T20:43:48.7323936Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-14T20:43:48.7399884Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 23.2 MB/s eta 0:00:00 2025-08-14T20:43:48.7431184Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-14T20:43:48.7513370Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 60.6 MB/s eta 0:00:00 2025-08-14T20:43:48.7546692Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-08-14T20:43:48.7652705Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 94.0 MB/s eta 0:00:00 2025-08-14T20:43:48.7697054Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-14T20:43:48.7769458Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-14T20:43:48.7845365Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 80.9 MB/s eta 0:00:00 2025-08-14T20:43:48.7879032Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-08-14T20:43:48.7919986Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 33.2 MB/s eta 0:00:00 2025-08-14T20:43:48.7962260Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-14T20:43:48.8004830Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 40.0 MB/s eta 0:00:00 2025-08-14T20:43:49.0870713Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-14T20:43:49.6176045Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-08-14T20:43:49.6941241Z ##[group]Run curr_branch="main" 2025-08-14T20:43:49.6941692Z curr_branch="main" 2025-08-14T20:43:49.6942043Z curr_ref_type="branch" 2025-08-14T20:43:49.6942447Z echo "Current branch is '$curr_branch'" 2025-08-14T20:43:49.6942715Z  2025-08-14T20:43:49.6942918Z python3 runner_determinator.py \ 2025-08-14T20:43:49.6943199Z  --github-token "$GITHUB_TOKEN" \ 2025-08-14T20:43:49.6943487Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-14T20:43:49.6943746Z  --github-branch "$curr_branch" \ 2025-08-14T20:43:49.6944013Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-14T20:43:49.6944311Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-14T20:43:49.6944597Z  --github-ref-type "$curr_ref_type" \ 2025-08-14T20:43:49.6944868Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-14T20:43:49.6945167Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-14T20:43:49.6945546Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-14T20:43:49.6945839Z  --pr-number "${PR_NUMBER}" 2025-08-14T20:43:49.6987998Z shell: /usr/bin/bash -e {0} 2025-08-14T20:43:49.6988247Z env: 2025-08-14T20:43:49.6988805Z GITHUB_TOKEN: *** 2025-08-14T20:43:49.6989010Z ISSUE_NUMBER: 5132 2025-08-14T20:43:49.6989212Z TRIGGERING_ACTOR: pytorchmergebot 2025-08-14T20:43:49.6989463Z ISSUE_OWNER: 2025-08-14T20:43:49.6989655Z CHECK_EXPERIMENTS: 2025-08-14T20:43:49.6989848Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:43:49.6990048Z PR_NUMBER: 2025-08-14T20:43:49.6990214Z ##[endgroup] 2025-08-14T20:43:49.7046927Z Current branch is 'main' 2025-08-14T20:43:51.1864038Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-14T20:43:51.1865288Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-14T20:43:51.1865984Z INFO : Setting output: label-type='' 2025-08-14T20:43:51.2183595Z Evaluate and set job outputs 2025-08-14T20:43:51.2190706Z Cleaning up orphan processes