2025-09-07T07:03:38.6146794Z Current runner version: '2.328.0' 2025-09-07T07:03:38.6171128Z ##[group]Runner Image Provisioner 2025-09-07T07:03:38.6171988Z Hosted Compute Agent 2025-09-07T07:03:38.6172627Z Version: 20250829.383 2025-09-07T07:03:38.6173727Z Commit: 27cb235aab5b0e52e153a26cd86b4742e89dac5d 2025-09-07T07:03:38.6174478Z Build Date: 2025-08-29T13:48:48Z 2025-09-07T07:03:38.6175036Z ##[endgroup] 2025-09-07T07:03:38.6175655Z ##[group]Operating System 2025-09-07T07:03:38.6176205Z Ubuntu 2025-09-07T07:03:38.6176683Z 24.04.3 2025-09-07T07:03:38.6177210Z LTS 2025-09-07T07:03:38.6177722Z ##[endgroup] 2025-09-07T07:03:38.6178200Z ##[group]Runner Image 2025-09-07T07:03:38.6178819Z Image: ubuntu-24.04 2025-09-07T07:03:38.6179343Z Version: 20250831.1.0 2025-09-07T07:03:38.6180328Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250831.1/images/ubuntu/Ubuntu2404-Readme.md 2025-09-07T07:03:38.6181943Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250831.1 2025-09-07T07:03:38.6183044Z ##[endgroup] 2025-09-07T07:03:38.6184171Z ##[group]GITHUB_TOKEN Permissions 2025-09-07T07:03:38.6186384Z Contents: read 2025-09-07T07:03:38.6186991Z Metadata: read 2025-09-07T07:03:38.6187527Z ##[endgroup] 2025-09-07T07:03:38.6189632Z Secret source: Actions 2025-09-07T07:03:38.6190418Z Prepare workflow directory 2025-09-07T07:03:38.6720381Z Prepare all required actions 2025-09-07T07:03:38.6776204Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (93fb23d6fae7c4e82c4239a1033e522088742634) 2025-09-07T07:03:38.6781089Z ##[group] Inputs 2025-09-07T07:03:38.6781724Z check_experiments: 2025-09-07T07:03:38.6782374Z opt_out_experiments: lf 2025-09-07T07:03:38.6783446Z triggering_actor: pytorchmergebot 2025-09-07T07:03:38.6784130Z issue_owner: 2025-09-07T07:03:38.6784692Z curr_branch: main 2025-09-07T07:03:38.6785257Z curr_ref_type: branch 2025-09-07T07:03:38.6785911Z issue_number: 5132 2025-09-07T07:03:38.6786501Z ##[endgroup] 2025-09-07T07:03:38.6787135Z Complete job name: get-label-type / runner-determinator 2025-09-07T07:03:39.3331234Z ##[group]Run cat < runner_determinator.py 2025-09-07T07:03:39.3333908Z cat < runner_determinator.py 2025-09-07T07:03:39.3334623Z # flake8: noqa: G004 2025-09-07T07:03:39.3335264Z  2025-09-07T07:03:39.3336068Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T07:03:39.3337213Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T07:03:39.3338189Z # python .github/scripts/update_runner_determinator.py 2025-09-07T07:03:39.3339023Z  2025-09-07T07:03:39.3339515Z """ 2025-09-07T07:03:39.3340287Z This runner determinator is used to determine which set of runners to run a 2025-09-07T07:03:39.3341448Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T07:03:39.3342824Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T07:03:39.3343975Z of which runners should be used to run which job. 2025-09-07T07:03:39.3344693Z  2025-09-07T07:03:39.3345451Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T07:03:39.3346643Z separated by a line containing "---". If the line is not present, the 2025-09-07T07:03:39.3347691Z settings are considered to be empty with only the second part, the user 2025-09-07T07:03:39.3348601Z list, defined. 2025-09-07T07:03:39.3349164Z  2025-09-07T07:03:39.3349929Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T07:03:39.3351023Z used to define any settings that are needed to determine which runners to use. 2025-09-07T07:03:39.3352084Z It's fields are defined by the RolloutSettings class below. 2025-09-07T07:03:39.3353218Z  2025-09-07T07:03:39.3354313Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T07:03:39.3355464Z The user list is also a comma separated list of additional features or 2025-09-07T07:03:39.3356403Z experiments which the user could be opted in to. 2025-09-07T07:03:39.3357133Z  2025-09-07T07:03:39.3357719Z The user list has the following rules: 2025-09-07T07:03:39.3358372Z  2025-09-07T07:03:39.3359099Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T07:03:39.3360120Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T07:03:39.3361163Z - A "#" prefix opts the user out of all experiments 2025-09-07T07:03:39.3361893Z  2025-09-07T07:03:39.3362345Z Example config: 2025-09-07T07:03:39.3363203Z  # A list of experiments that can be opted into. 2025-09-07T07:03:39.3364081Z  # This defines the behavior they'll induce when opted into. 2025-09-07T07:03:39.3364885Z  # Expected syntax is: 2025-09-07T07:03:39.3365782Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T07:03:39.3366940Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T07:03:39.3367835Z  2025-09-07T07:03:39.3368374Z  experiments: 2025-09-07T07:03:39.3369118Z  lf: 2025-09-07T07:03:39.3369646Z  rollout_percent: 25 2025-09-07T07:03:39.3370326Z  all_branches: false 2025-09-07T07:03:39.3370930Z  default: true 2025-09-07T07:03:39.3371515Z  --- 2025-09-07T07:03:39.3372081Z  2025-09-07T07:03:39.3372571Z  # Opt-ins: 2025-09-07T07:03:39.3373488Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T07:03:39.3374769Z  # and specifying experiments to enable in a comma-separated list. 2025-09-07T07:03:39.3375793Z  # To always opt out of an experiment, prefix it with a "-". 2025-09-07T07:03:39.3376661Z  # Experiments should be from the above list. 2025-09-07T07:03:39.3377369Z  2025-09-07T07:03:39.3377949Z  @User1,-lf,split_build 2025-09-07T07:03:39.3378541Z  @User2,lf 2025-09-07T07:03:39.3379183Z  @User3,split_build 2025-09-07T07:03:39.3379776Z """ 2025-09-07T07:03:39.3380280Z  2025-09-07T07:03:39.3380727Z import json 2025-09-07T07:03:39.3381377Z import logging 2025-09-07T07:03:39.3381944Z import os 2025-09-07T07:03:39.3382899Z import random 2025-09-07T07:03:39.3457772Z import re 2025-09-07T07:03:39.3458254Z import sys 2025-09-07T07:03:39.3458774Z from argparse import ArgumentParser 2025-09-07T07:03:39.3459473Z from collections.abc import Iterable 2025-09-07T07:03:39.3460076Z from functools import cache 2025-09-07T07:03:39.3460618Z from logging import LogRecord 2025-09-07T07:03:39.3461238Z from typing import Any, NamedTuple 2025-09-07T07:03:39.3461869Z from urllib.request import Request, urlopen 2025-09-07T07:03:39.3462460Z  2025-09-07T07:03:39.3462963Z import yaml 2025-09-07T07:03:39.3463435Z from github import Auth, Github 2025-09-07T07:03:39.3463993Z from github.Issue import Issue 2025-09-07T07:03:39.3464520Z  2025-09-07T07:03:39.3464885Z  2025-09-07T07:03:39.3465343Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T07:03:39.3466120Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T07:03:39.3467091Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T07:03:39.3467854Z  2025-09-07T07:03:39.3468631Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T07:03:39.3469275Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T07:03:39.3469868Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T07:03:39.3470513Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T07:03:39.3471079Z  2025-09-07T07:03:39.3471508Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T07:03:39.3472046Z  2025-09-07T07:03:39.3472443Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T07:03:39.3473196Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T07:03:39.3473691Z  2025-09-07T07:03:39.3474060Z  2025-09-07T07:03:39.3474459Z class Experiment(NamedTuple): 2025-09-07T07:03:39.3475007Z  rollout_perc: float = ( 2025-09-07T07:03:39.3475755Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T07:03:39.3476479Z  ) 2025-09-07T07:03:39.3476895Z  all_branches: bool = ( 2025-09-07T07:03:39.3477620Z  False # If True, the experiment is also enabled on the exception branches 2025-09-07T07:03:39.3478365Z  ) 2025-09-07T07:03:39.3478781Z  default: bool = ( 2025-09-07T07:03:39.3479438Z  True # If True, the experiment is enabled by default for all queries 2025-09-07T07:03:39.3480130Z  ) 2025-09-07T07:03:39.3480511Z  2025-09-07T07:03:39.3480916Z  # Add more fields as needed 2025-09-07T07:03:39.3481448Z  2025-09-07T07:03:39.3481817Z  2025-09-07T07:03:39.3482222Z class Settings(NamedTuple): 2025-09-07T07:03:39.3482879Z  """ 2025-09-07T07:03:39.3483418Z  Settings for the experiments that can be opted into. 2025-09-07T07:03:39.3484044Z  """ 2025-09-07T07:03:39.3484443Z  2025-09-07T07:03:39.3484890Z  experiments: dict[str, Experiment] = {} 2025-09-07T07:03:39.3485443Z  2025-09-07T07:03:39.3485957Z  2025-09-07T07:03:39.3486420Z class ColorFormatter(logging.Formatter): 2025-09-07T07:03:39.3487119Z  """Color codes the log messages based on the log level""" 2025-09-07T07:03:39.3487754Z  2025-09-07T07:03:39.3488127Z  COLORS = { 2025-09-07T07:03:39.3488620Z  "WARNING": "\033[33m", # Yellow 2025-09-07T07:03:39.3489182Z  "ERROR": "\033[31m", # Red 2025-09-07T07:03:39.3489741Z  "CRITICAL": "\033[31m", # Red 2025-09-07T07:03:39.3490294Z  "INFO": "\033[0m", # Reset 2025-09-07T07:03:39.3490843Z  "DEBUG": "\033[0m", # Reset 2025-09-07T07:03:39.3491387Z  } 2025-09-07T07:03:39.3491773Z  2025-09-07T07:03:39.3492232Z  def format(self, record: LogRecord) -> str: 2025-09-07T07:03:39.3493159Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T07:03:39.3494017Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T07:03:39.3494652Z  return super().format(record) 2025-09-07T07:03:39.3495179Z  2025-09-07T07:03:39.3495538Z  2025-09-07T07:03:39.3495953Z handler = logging.StreamHandler() 2025-09-07T07:03:39.3496750Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T07:03:39.3497517Z  2025-09-07T07:03:39.3498020Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T07:03:39.3498700Z log.addHandler(handler) 2025-09-07T07:03:39.3499212Z log.setLevel(logging.INFO) 2025-09-07T07:03:39.3499705Z  2025-09-07T07:03:39.3500077Z  2025-09-07T07:03:39.3500584Z def set_github_output(key: str, value: str) -> None: 2025-09-07T07:03:39.3501200Z  """ 2025-09-07T07:03:39.3501771Z  Defines outputs of the github action that invokes this script 2025-09-07T07:03:39.3502591Z  """ 2025-09-07T07:03:39.3503122Z  if not GITHUB_OUTPUT: 2025-09-07T07:03:39.3504286Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T07:03:39.3505477Z  log.warning( 2025-09-07T07:03:39.3506431Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T07:03:39.3507399Z  ) 2025-09-07T07:03:39.3507902Z  print(f"::set-output name={key}::{value}") 2025-09-07T07:03:39.3508490Z  return 2025-09-07T07:03:39.3508909Z  2025-09-07T07:03:39.3509339Z  with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T07:03:39.3509966Z  log.info(f"Setting output: {key}='{value}'") 2025-09-07T07:03:39.3510587Z  f.write(f"{key}={value}\n") 2025-09-07T07:03:39.3511124Z  2025-09-07T07:03:39.3511513Z  2025-09-07T07:03:39.3512077Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T07:03:39.3513073Z  return frozenset( 2025-09-07T07:03:39.3513780Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T07:03:39.3514502Z  ) 2025-09-07T07:03:39.3514903Z  2025-09-07T07:03:39.3515293Z  2025-09-07T07:03:39.3515704Z def parse_args() -> Any: 2025-09-07T07:03:39.3516368Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T07:03:39.3517323Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T07:03:39.3518150Z  parser.add_argument( 2025-09-07T07:03:39.3518697Z  "--github-issue-repo", 2025-09-07T07:03:39.3519239Z  type=str, 2025-09-07T07:03:39.3519718Z  required=False, 2025-09-07T07:03:39.3520396Z  default="pytorch/test-infra", 2025-09-07T07:03:39.3521038Z  help="GitHub repo to get the issue", 2025-09-07T07:03:39.3521607Z  ) 2025-09-07T07:03:39.3522050Z  parser.add_argument( 2025-09-07T07:03:39.3522580Z  "--github-repo", 2025-09-07T07:03:39.3523211Z  type=str, 2025-09-07T07:03:39.3523694Z  required=True, 2025-09-07T07:03:39.3524256Z  help="GitHub repo where CI is running", 2025-09-07T07:03:39.3524837Z  ) 2025-09-07T07:03:39.3525270Z  parser.add_argument( 2025-09-07T07:03:39.3525978Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T07:03:39.3526694Z  ) 2025-09-07T07:03:39.3527129Z  parser.add_argument( 2025-09-07T07:03:39.3527859Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T07:03:39.3528612Z  ) 2025-09-07T07:03:39.3529051Z  parser.add_argument( 2025-09-07T07:03:39.3529784Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T07:03:39.3530536Z  ) 2025-09-07T07:03:39.3530967Z  parser.add_argument( 2025-09-07T07:03:39.3531738Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T07:03:39.3532510Z  ) 2025-09-07T07:03:39.3533067Z  parser.add_argument( 2025-09-07T07:03:39.3533611Z  "--github-ref-type", 2025-09-07T07:03:39.3534158Z  type=str, 2025-09-07T07:03:39.3534662Z  required=True, 2025-09-07T07:03:39.3535285Z  help="Current GitHub ref type, branch or tag", 2025-09-07T07:03:39.3535914Z  ) 2025-09-07T07:03:39.3536364Z  parser.add_argument( 2025-09-07T07:03:39.3536925Z  "--eligible-experiments", 2025-09-07T07:03:39.3537685Z  type=_str_comma_separated_to_set, 2025-09-07T07:03:39.3538285Z  required=False, 2025-09-07T07:03:39.3538808Z  default="", 2025-09-07T07:03:39.3539781Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T07:03:39.3540830Z  ) 2025-09-07T07:03:39.3541268Z  parser.add_argument( 2025-09-07T07:03:39.3541817Z  "--opt-out-experiments", 2025-09-07T07:03:39.3542426Z  type=_str_comma_separated_to_set, 2025-09-07T07:03:39.3543179Z  required=False, 2025-09-07T07:03:39.3543703Z  default="", 2025-09-07T07:03:39.3544188Z  help=( 2025-09-07T07:03:39.3544981Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T07:03:39.3546241Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T07:03:39.3547146Z  ), 2025-09-07T07:03:39.3547587Z  ) 2025-09-07T07:03:39.3548036Z  parser.add_argument( 2025-09-07T07:03:39.3548576Z  "--pr-number", 2025-09-07T07:03:39.3549098Z  type=str, 2025-09-07T07:03:39.3549598Z  required=False, 2025-09-07T07:03:39.3550120Z  default="", 2025-09-07T07:03:39.3550731Z  help="the optional PR number where this is run", 2025-09-07T07:03:39.3551372Z  ) 2025-09-07T07:03:39.3551790Z  2025-09-07T07:03:39.3552227Z  return parser.parse_args() 2025-09-07T07:03:39.3553242Z  2025-09-07T07:03:39.3553658Z  2025-09-07T07:03:39.3554338Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T07:03:39.3555341Z  auth = Auth.Token(github_token) 2025-09-07T07:03:39.3555966Z  return Github(auth=auth) 2025-09-07T07:03:39.3556508Z  2025-09-07T07:03:39.3556899Z  2025-09-07T07:03:39.3557643Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T07:03:39.3558537Z  repo = gh.get_repo(repo) 2025-09-07T07:03:39.3559145Z  return repo.get_issue(number=issue_num) 2025-09-07T07:03:39.3559753Z  2025-09-07T07:03:39.3560151Z  2025-09-07T07:03:39.3560571Z def get_potential_pr_author( 2025-09-07T07:03:39.3561338Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T07:03:39.3562095Z ) -> str: 2025-09-07T07:03:39.3562846Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T07:03:39.3563771Z  # Fetch the actual username from the original PR. The PR number is 2025-09-07T07:03:39.3564643Z  # embedded in the tag name: ciflow// 2025-09-07T07:03:39.3565279Z  2025-09-07T07:03:39.3565725Z  gh = get_gh_client(github_token) 2025-09-07T07:03:39.3566293Z  2025-09-07T07:03:39.3566833Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T07:03:39.3567551Z  split_tag = ref_name.split("/") 2025-09-07T07:03:39.3568122Z  if ( 2025-09-07T07:03:39.3568605Z  len(split_tag) == 3 2025-09-07T07:03:39.3569189Z  and split_tag[0] == "ciflow" 2025-09-07T07:03:39.3569814Z  and split_tag[2].isnumeric() 2025-09-07T07:03:39.3570395Z  ): 2025-09-07T07:03:39.3570886Z  pr_number = split_tag[2] 2025-09-07T07:03:39.3571465Z  try: 2025-09-07T07:03:39.3572001Z  repository = gh.get_repo(repo) 2025-09-07T07:03:39.3572957Z  pull = repository.get_pull(number=int(pr_number)) 2025-09-07T07:03:39.3573649Z  except Exception as e: 2025-09-07T07:03:39.3574270Z  raise Exception( # noqa: TRY002 2025-09-07T07:03:39.3575039Z  f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T07:03:39.3575758Z  ) from e 2025-09-07T07:03:39.3576428Z  return pull.user.login # type: ignore[no-any-return] 2025-09-07T07:03:39.3577230Z  # In all other cases, return the original input username 2025-09-07T07:03:39.3577913Z  return username 2025-09-07T07:03:39.3578397Z  2025-09-07T07:03:39.3578801Z  2025-09-07T07:03:39.3579296Z def is_exception_branch(branch: str) -> bool: 2025-09-07T07:03:39.3579908Z  """ 2025-09-07T07:03:39.3580667Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T07:03:39.3581539Z  """ 2025-09-07T07:03:39.3582183Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T07:03:39.3583016Z  2025-09-07T07:03:39.3583421Z  2025-09-07T07:03:39.3583875Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T07:03:39.3584458Z  try: 2025-09-07T07:03:39.3584948Z  data = yaml.safe_load(yaml_text) 2025-09-07T07:03:39.3585533Z  return data 2025-09-07T07:03:39.3586059Z  except yaml.YAMLError: 2025-09-07T07:03:39.3586647Z  log.exception("Error loading YAML") 2025-09-07T07:03:39.3587239Z  raise 2025-09-07T07:03:39.3587691Z  2025-09-07T07:03:39.3588080Z  2025-09-07T07:03:39.3588769Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T07:03:39.3589592Z  """ 2025-09-07T07:03:39.3590450Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T07:03:39.3591275Z  2025-09-07T07:03:39.3591899Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T07:03:39.3592859Z  and the text below is the list of opted in users. 2025-09-07T07:03:39.3593500Z  2025-09-07T07:03:39.3594154Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T07:03:39.3594925Z  """ 2025-09-07T07:03:39.3595462Z  rollout_state_parts = rollout_state.split("---") 2025-09-07T07:03:39.3596148Z  if len(rollout_state_parts) >= 2: 2025-09-07T07:03:39.3596862Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T07:03:39.3597523Z  else: 2025-09-07T07:03:39.3598001Z  return "", rollout_state 2025-09-07T07:03:39.3598537Z  2025-09-07T07:03:39.3598932Z  2025-09-07T07:03:39.3599648Z class UserOptins(dict[str, list[str]]): 2025-09-07T07:03:39.3600237Z  """ 2025-09-07T07:03:39.3600857Z  Dictionary of users with a list of features they have opted into 2025-09-07T07:03:39.3601566Z  """ 2025-09-07T07:03:39.3601978Z  2025-09-07T07:03:39.3602364Z  2025-09-07T07:03:39.3603079Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T07:03:39.3603823Z  """ 2025-09-07T07:03:39.3604642Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T07:03:39.3605550Z  2025-09-07T07:03:39.3606426Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T07:03:39.3607528Z  - Example line: "@User1,lf,split_build" 2025-09-07T07:03:39.3608448Z  - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T07:03:39.3609151Z  2025-09-07T07:03:39.3609540Z  2025-09-07T07:03:39.3609923Z  """ 2025-09-07T07:03:39.3610370Z  optins = UserOptins() 2025-09-07T07:03:39.3610963Z  for user in user_optin_text.split("\n"): 2025-09-07T07:03:39.3611605Z  user = user.strip("\r\n\t -") 2025-09-07T07:03:39.3612278Z  if not user or not user.startswith("@"): 2025-09-07T07:03:39.3613027Z  # Not a valid user. Skip 2025-09-07T07:03:39.3613594Z  continue 2025-09-07T07:03:39.3614079Z  2025-09-07T07:03:39.3614479Z  if user: 2025-09-07T07:03:39.3615031Z  usr_name = user.split(",")[0].strip("@") 2025-09-07T07:03:39.3615818Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T07:03:39.3616557Z  2025-09-07T07:03:39.3616971Z  return optins 2025-09-07T07:03:39.3617443Z  2025-09-07T07:03:39.3617825Z  2025-09-07T07:03:39.3618384Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T07:03:39.3619066Z  """ 2025-09-07T07:03:39.3619548Z  Check if the experiment name is valid. 2025-09-07T07:03:39.3620143Z  A valid name: 2025-09-07T07:03:39.3620936Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T07:03:39.3621976Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T07:03:39.3622863Z  - Cannot contain spaces 2025-09-07T07:03:39.3623412Z  """ 2025-09-07T07:03:39.3623816Z  2025-09-07T07:03:39.3624343Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T07:03:39.3625149Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T07:03:39.3625943Z  2025-09-07T07:03:39.3626348Z  if valid: 2025-09-07T07:03:39.3626823Z  return True 2025-09-07T07:03:39.3627292Z  2025-09-07T07:03:39.3627695Z  log.error( 2025-09-07T07:03:39.3629269Z  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-09-07T07:03:39.3630902Z  ) 2025-09-07T07:03:39.3631340Z  return False 2025-09-07T07:03:39.3631804Z  2025-09-07T07:03:39.3632195Z  2025-09-07T07:03:39.3632883Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T07:03:39.3633616Z  """ 2025-09-07T07:03:39.3634306Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T07:03:39.3635095Z  """ 2025-09-07T07:03:39.3635521Z  try: 2025-09-07T07:03:39.3635966Z  if settings_text: 2025-09-07T07:03:39.3636797Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T07:03:39.3637665Z  # for easy reading 2025-09-07T07:03:39.3638575Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T07:03:39.3639582Z  # the backtick character in shell commands. 2025-09-07T07:03:39.3640304Z  backtick = chr(96) # backtick character 2025-09-07T07:03:39.3641118Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T07:03:39.3641871Z  settings = load_yaml(settings_text) 2025-09-07T07:03:39.3642457Z  2025-09-07T07:03:39.3643257Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T07:03:39.3644208Z  experiments = {} 2025-09-07T07:03:39.3644742Z  2025-09-07T07:03:39.3645373Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T07:03:39.3646237Z  if not is_valid_experiment_name(exp_name): 2025-09-07T07:03:39.3647449Z  # 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-09-07T07:03:39.3648569Z  continue 2025-09-07T07:03:39.3649088Z  2025-09-07T07:03:39.3649525Z  valid_settings = {} 2025-09-07T07:03:39.3650138Z  for setting in exp_settings: 2025-09-07T07:03:39.3650788Z  if setting not in Experiment._fields: 2025-09-07T07:03:39.3651442Z  log.warning( 2025-09-07T07:03:39.3652257Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T07:03:39.3653136Z  ) 2025-09-07T07:03:39.3653662Z  else: 2025-09-07T07:03:39.3654296Z  valid_settings[setting] = exp_settings[setting] 2025-09-07T07:03:39.3654949Z  2025-09-07T07:03:39.3655487Z  experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T07:03:39.3656216Z  return Settings(experiments) 2025-09-07T07:03:39.3656787Z  2025-09-07T07:03:39.3657193Z  except Exception: 2025-09-07T07:03:39.3657782Z  log.exception("Failed to parse settings") 2025-09-07T07:03:39.3658378Z  2025-09-07T07:03:39.3658786Z  return Settings() 2025-09-07T07:03:39.3659263Z  2025-09-07T07:03:39.3659642Z  2025-09-07T07:03:39.3660333Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T07:03:39.3660990Z  """ 2025-09-07T07:03:39.3661531Z  Parse settings, if any, from the rollout state. 2025-09-07T07:03:39.3662147Z  2025-09-07T07:03:39.3662866Z  If the issue body contains "---" then the text above that is the settings 2025-09-07T07:03:39.3663734Z  and the text below is the list of opted in users. 2025-09-07T07:03:39.3664347Z  2025-09-07T07:03:39.3665013Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T07:03:39.3665814Z  """ 2025-09-07T07:03:39.3666468Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:03:39.3667337Z  return parse_settings_from_text(settings_text) 2025-09-07T07:03:39.3667953Z  2025-09-07T07:03:39.3668348Z  2025-09-07T07:03:39.3668873Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T07:03:39.3669508Z  """ 2025-09-07T07:03:39.3669991Z  Parse users from the rollout state. 2025-09-07T07:03:39.3670566Z  2025-09-07T07:03:39.3670956Z  """ 2025-09-07T07:03:39.3671594Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:03:39.3672433Z  return parse_user_opt_in_from_text(users_text) 2025-09-07T07:03:39.3673149Z  2025-09-07T07:03:39.3673533Z  2025-09-07T07:03:39.3674240Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:03:39.3675058Z  """ 2025-09-07T07:03:39.3675568Z  Check if a user is opted into an experiment 2025-09-07T07:03:39.3676169Z  """ 2025-09-07T07:03:39.3676723Z  return experiment_name in user_optins.get(user, []) 2025-09-07T07:03:39.3677373Z  2025-09-07T07:03:39.3677898Z  2025-09-07T07:03:39.3678607Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:03:39.3679428Z  """ 2025-09-07T07:03:39.3679986Z  Check if a user explicitly opted out of an experiment 2025-09-07T07:03:39.3680641Z  """ 2025-09-07T07:03:39.3681245Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T07:03:39.3682034Z  experiment_optout = "-" + experiment_name 2025-09-07T07:03:39.3682871Z  if experiment_optout not in user_optins.get(user, []): 2025-09-07T07:03:39.3683562Z  return False 2025-09-07T07:03:39.3684037Z  2025-09-07T07:03:39.3684579Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T07:03:39.3685259Z  log.warning( 2025-09-07T07:03:39.3686185Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T07:03:39.3687140Z  ) 2025-09-07T07:03:39.3687565Z  2025-09-07T07:03:39.3687974Z  return True 2025-09-07T07:03:39.3688435Z  2025-09-07T07:03:39.3688816Z  2025-09-07T07:03:39.3689224Z def get_runner_prefix( 2025-09-07T07:03:39.3689753Z  rollout_state: str, 2025-09-07T07:03:39.3690313Z  workflow_requestors: Iterable[str], 2025-09-07T07:03:39.3690903Z  branch: str, 2025-09-07T07:03:39.3691507Z  eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T07:03:39.3692262Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T07:03:39.3693028Z  is_canary: bool = False, 2025-09-07T07:03:39.3693555Z ) -> str: 2025-09-07T07:03:39.3694077Z  settings = parse_settings(rollout_state) 2025-09-07T07:03:39.3694754Z  user_optins = parse_users(rollout_state) 2025-09-07T07:03:39.3695341Z  2025-09-07T07:03:39.3695876Z  fleet_prefix = "" 2025-09-07T07:03:39.3696390Z  prefixes = [] 2025-09-07T07:03:39.3697134Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T07:03:39.3698182Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T07:03:39.3698981Z  log.info( 2025-09-07T07:03:39.3699775Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T07:03:39.3700593Z  ) 2025-09-07T07:03:39.3701076Z  continue 2025-09-07T07:03:39.3701553Z  2025-09-07T07:03:39.3701986Z  if opt_out_experiments: 2025-09-07T07:03:39.3702630Z  if experiment_name in opt_out_experiments: 2025-09-07T07:03:39.3703484Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T07:03:39.3704166Z  log.info( 2025-09-07T07:03:39.3705204Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T07:03:39.3706248Z  ) 2025-09-07T07:03:39.3706726Z  continue 2025-09-07T07:03:39.3707237Z  2025-09-07T07:03:39.3707665Z  if eligible_experiments: 2025-09-07T07:03:39.3708364Z  if experiment_name not in eligible_experiments: 2025-09-07T07:03:39.3709097Z  exp_list = ", ".join(eligible_experiments) 2025-09-07T07:03:39.3709713Z  log.info( 2025-09-07T07:03:39.3710593Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T07:03:39.3711491Z  ) 2025-09-07T07:03:39.3711977Z  continue 2025-09-07T07:03:39.3712787Z  elif not experiment_settings.default: 2025-09-07T07:03:39.3713403Z  log.info( 2025-09-07T07:03:39.3714171Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T07:03:39.3714976Z  ) 2025-09-07T07:03:39.3715428Z  continue 2025-09-07T07:03:39.3715893Z  2025-09-07T07:03:39.3716433Z  # Is any workflow_requestor opted out to this experiment? 2025-09-07T07:03:39.3717126Z  opted_out_users = [ 2025-09-07T07:03:39.3717673Z  requestor 2025-09-07T07:03:39.3718233Z  for requestor in workflow_requestors 2025-09-07T07:03:39.3718992Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T07:03:39.3719692Z  ] 2025-09-07T07:03:39.3720110Z  2025-09-07T07:03:39.3720528Z  if opted_out_users: 2025-09-07T07:03:39.3721092Z  log.info( 2025-09-07T07:03:39.3721832Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T07:03:39.3722599Z  ) 2025-09-07T07:03:39.3723158Z  continue 2025-09-07T07:03:39.3723642Z  2025-09-07T07:03:39.3724173Z  # Is any workflow_requestor opted in to this experiment? 2025-09-07T07:03:39.3724866Z  opted_in_users = [ 2025-09-07T07:03:39.3725405Z  requestor 2025-09-07T07:03:39.3725974Z  for requestor in workflow_requestors 2025-09-07T07:03:39.3726727Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T07:03:39.3727417Z  ] 2025-09-07T07:03:39.3727845Z  2025-09-07T07:03:39.3728253Z  enabled = False 2025-09-07T07:03:39.3728781Z  if opted_in_users: 2025-09-07T07:03:39.3729447Z  log.info( 2025-09-07T07:03:39.3730180Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T07:03:39.3730930Z  ) 2025-09-07T07:03:39.3731409Z  enabled = True 2025-09-07T07:03:39.3731920Z  2025-09-07T07:03:39.3732393Z  elif experiment_settings.rollout_perc: 2025-09-07T07:03:39.3733434Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T07:03:39.3734474Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T07:03:39.3735204Z  log.info( 2025-09-07T07:03:39.3736183Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T07:03:39.3737179Z  ) 2025-09-07T07:03:39.3737695Z  enabled = True 2025-09-07T07:03:39.3738222Z  2025-09-07T07:03:39.3738633Z  if enabled: 2025-09-07T07:03:39.3739174Z  label = experiment_name 2025-09-07T07:03:39.3739815Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T07:03:39.3740779Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T07:03:39.3741742Z  # - If it's enabled, then we always list it's prefix first 2025-09-07T07:03:39.3742602Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T07:03:39.3743471Z  if is_canary: 2025-09-07T07:03:39.3744063Z  label += CANARY_FLEET_SUFFIX 2025-09-07T07:03:39.3744692Z  fleet_prefix = label 2025-09-07T07:03:39.3745264Z  else: 2025-09-07T07:03:39.3745936Z  prefixes.append(label) 2025-09-07T07:03:39.3746485Z  2025-09-07T07:03:39.3746894Z  if len(prefixes) > 1: 2025-09-07T07:03:39.3747413Z  log.error( 2025-09-07T07:03:39.3748577Z  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-09-07T07:03:39.3749767Z  ) 2025-09-07T07:03:39.3750245Z  prefixes = prefixes[:1] 2025-09-07T07:03:39.3750781Z  2025-09-07T07:03:39.3751205Z  # Fleet always comes first 2025-09-07T07:03:39.3751765Z  if fleet_prefix: 2025-09-07T07:03:39.3752311Z  prefixes.insert(0, fleet_prefix) 2025-09-07T07:03:39.3752988Z  2025-09-07T07:03:39.3753498Z  return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T07:03:39.3754143Z  2025-09-07T07:03:39.3754542Z  2025-09-07T07:03:39.3755265Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T07:03:39.3756105Z  """ 2025-09-07T07:03:39.3756777Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T07:03:39.3757552Z  2025-09-07T07:03:39.3758192Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T07:03:39.3758960Z  """ 2025-09-07T07:03:39.3759435Z  gh = get_gh_client(github_token) 2025-09-07T07:03:39.3760069Z  issue = get_issue(gh, repo, issue_num) 2025-09-07T07:03:39.3760807Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T07:03:39.3761476Z  2025-09-07T07:03:39.3761867Z  2025-09-07T07:03:39.3762528Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T07:03:39.3763595Z  for _ in range(num_retries): 2025-09-07T07:03:39.3764170Z  try: 2025-09-07T07:03:39.3764687Z  req = Request(url=url, headers=headers) 2025-09-07T07:03:39.3765444Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T07:03:39.3766163Z  return json.loads(content) 2025-09-07T07:03:39.3766784Z  except Exception as e: 2025-09-07T07:03:39.3767432Z  log.warning(f"Could not download {url}: {e}") 2025-09-07T07:03:39.3768052Z  2025-09-07T07:03:39.3768700Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T07:03:39.3769479Z  return {} 2025-09-07T07:03:39.3769932Z  2025-09-07T07:03:39.3770316Z  2025-09-07T07:03:39.3770701Z @cache 2025-09-07T07:03:39.3771420Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T07:03:39.3772262Z  """ 2025-09-07T07:03:39.3772855Z  Dynamically get PR information 2025-09-07T07:03:39.3773426Z  """ 2025-09-07T07:03:39.3774023Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T07:03:39.3774715Z  headers = { 2025-09-07T07:03:39.3775282Z  "Accept": "application/vnd.github.v3+json", 2025-09-07T07:03:39.3775968Z  "Authorization": f"token {github_token}", 2025-09-07T07:03:39.3776562Z  } 2025-09-07T07:03:39.3777074Z  json_response: dict[str, Any] = download_json( 2025-09-07T07:03:39.3777765Z  url=f"{github_api}/issues/{pr_number}", 2025-09-07T07:03:39.3778373Z  headers=headers, 2025-09-07T07:03:39.3778882Z  ) 2025-09-07T07:03:39.3779292Z  2025-09-07T07:03:39.3779706Z  if not json_response: 2025-09-07T07:03:39.3780378Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T07:03:39.3781210Z  return {} 2025-09-07T07:03:39.3781685Z  2025-09-07T07:03:39.3782093Z  return json_response 2025-09-07T07:03:39.3782609Z  2025-09-07T07:03:39.3783099Z  2025-09-07T07:03:39.3783772Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T07:03:39.3784569Z  """ 2025-09-07T07:03:39.3785186Z  Dynamically get the latest list of labels from the pull request 2025-09-07T07:03:39.3785905Z  """ 2025-09-07T07:03:39.3786476Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T07:03:39.3787161Z  return { 2025-09-07T07:03:39.3787835Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T07:03:39.3788588Z  } 2025-09-07T07:03:39.3788998Z  2025-09-07T07:03:39.3789388Z  2025-09-07T07:03:39.3789813Z def main() -> None: 2025-09-07T07:03:39.3790318Z  args = parse_args() 2025-09-07T07:03:39.3790818Z  2025-09-07T07:03:39.3791295Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T07:03:39.3791898Z  2025-09-07T07:03:39.3792329Z  # Check if the PR is opt-out 2025-09-07T07:03:39.3793000Z  if args.pr_number: 2025-09-07T07:03:39.3793776Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T07:03:39.3794607Z  if OPT_OUT_LABEL in labels: 2025-09-07T07:03:39.3795185Z  log.info( 2025-09-07T07:03:39.3795978Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T07:03:39.3796811Z  ) 2025-09-07T07:03:39.3797469Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:03:39.3798222Z  sys.exit() 2025-09-07T07:03:39.3798838Z  2025-09-07T07:03:39.3799232Z  try: 2025-09-07T07:03:39.3799761Z  rollout_state = get_rollout_state_from_issue( 2025-09-07T07:03:39.3800573Z  args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T07:03:39.3801280Z  ) 2025-09-07T07:03:39.3801700Z  2025-09-07T07:03:39.3802160Z  username = get_potential_pr_author( 2025-09-07T07:03:39.3802870Z  args.github_token, 2025-09-07T07:03:39.3803435Z  args.github_repo, 2025-09-07T07:03:39.3804001Z  args.github_actor, 2025-09-07T07:03:39.3804574Z  args.github_ref_type, 2025-09-07T07:03:39.3805153Z  args.github_branch, 2025-09-07T07:03:39.3805683Z  ) 2025-09-07T07:03:39.3806121Z  2025-09-07T07:03:39.3806674Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T07:03:39.3807355Z  2025-09-07T07:03:39.3807835Z  runner_label_prefix = get_runner_prefix( 2025-09-07T07:03:39.3808447Z  rollout_state, 2025-09-07T07:03:39.3809035Z  (args.github_issue_owner, username), 2025-09-07T07:03:39.3809646Z  args.github_branch, 2025-09-07T07:03:39.3810234Z  args.eligible_experiments, 2025-09-07T07:03:39.3810853Z  args.opt_out_experiments, 2025-09-07T07:03:39.3811430Z  is_canary, 2025-09-07T07:03:39.3811922Z  ) 2025-09-07T07:03:39.3812347Z  2025-09-07T07:03:39.3812909Z  except Exception as e: 2025-09-07T07:03:39.3813442Z  log.error( 2025-09-07T07:03:39.3814232Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T07:03:39.3815049Z  ) 2025-09-07T07:03:39.3815624Z  2025-09-07T07:03:39.3816207Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:03:39.3816922Z  2025-09-07T07:03:39.3817321Z  2025-09-07T07:03:39.3817726Z if __name__ == "__main__": 2025-09-07T07:03:39.3818248Z  main() 2025-09-07T07:03:39.3818676Z  2025-09-07T07:03:39.3819078Z EOF 2025-09-07T07:03:39.3819471Z  2025-09-07T07:03:39.3819899Z cat runner_determinator.py 2025-09-07T07:03:39.5154730Z shell: /usr/bin/bash -e {0} 2025-09-07T07:03:39.5155643Z env: 2025-09-07T07:03:39.5156421Z GITHUB_TOKEN: *** 2025-09-07T07:03:39.5156866Z ISSUE_NUMBER: 5132 2025-09-07T07:03:39.5157343Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:03:39.5157878Z ISSUE_OWNER: 2025-09-07T07:03:39.5158303Z CHECK_EXPERIMENTS: 2025-09-07T07:03:39.5158789Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:03:39.5159293Z PR_NUMBER: 2025-09-07T07:03:39.5159745Z ##[endgroup] 2025-09-07T07:03:39.5365396Z # flake8: noqa: G004 2025-09-07T07:03:39.5365793Z 2025-09-07T07:03:39.5366237Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-09-07T07:03:39.5367206Z # must be kept in sync. You can do it easily by running the following command: 2025-09-07T07:03:39.5368031Z # python .github/scripts/update_runner_determinator.py 2025-09-07T07:03:39.5368497Z 2025-09-07T07:03:39.5368668Z """ 2025-09-07T07:03:39.5369250Z This runner determinator is used to determine which set of runners to run a 2025-09-07T07:03:39.5370156Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-09-07T07:03:39.5371043Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-09-07T07:03:39.5371867Z of which runners should be used to run which job. 2025-09-07T07:03:39.5372265Z 2025-09-07T07:03:39.5372650Z The configuration has two parts, the settings and a list of opted-in users, 2025-09-07T07:03:39.5374051Z separated by a line containing "---". If the line is not present, the 2025-09-07T07:03:39.5374963Z settings are considered to be empty with only the second part, the user 2025-09-07T07:03:39.5375649Z list, defined. 2025-09-07T07:03:39.5375877Z 2025-09-07T07:03:39.5376237Z The first part is a YAML block that defines the rollout settings. This can be 2025-09-07T07:03:39.5377152Z used to define any settings that are needed to determine which runners to use. 2025-09-07T07:03:39.5377979Z It's fields are defined by the RolloutSettings class below. 2025-09-07T07:03:39.5378417Z 2025-09-07T07:03:39.5378785Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-09-07T07:03:39.5379631Z The user list is also a comma separated list of additional features or 2025-09-07T07:03:39.5380363Z experiments which the user could be opted in to. 2025-09-07T07:03:39.5380761Z 2025-09-07T07:03:39.5380958Z The user list has the following rules: 2025-09-07T07:03:39.5381300Z 2025-09-07T07:03:39.5381619Z - Users are GitHub usernames, which must start with the @ prefix 2025-09-07T07:03:39.5382464Z - Each user is also a comma-separated list of features/experiments to enable 2025-09-07T07:03:39.5383609Z - A "#" prefix opts the user out of all experiments 2025-09-07T07:03:39.5384030Z 2025-09-07T07:03:39.5384219Z Example config: 2025-09-07T07:03:39.5384664Z # A list of experiments that can be opted into. 2025-09-07T07:03:39.5385341Z # This defines the behavior they'll induce when opted into. 2025-09-07T07:03:39.5385966Z # Expected syntax is: 2025-09-07T07:03:39.5386627Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-09-07T07:03:39.5387610Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-09-07T07:03:39.5388233Z 2025-09-07T07:03:39.5388412Z experiments: 2025-09-07T07:03:39.5388810Z lf: 2025-09-07T07:03:39.5389181Z rollout_percent: 25 2025-09-07T07:03:39.5389636Z all_branches: false 2025-09-07T07:03:39.5390266Z default: true 2025-09-07T07:03:39.5390702Z --- 2025-09-07T07:03:39.5390918Z 2025-09-07T07:03:39.5391097Z # Opt-ins: 2025-09-07T07:03:39.5391705Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-09-07T07:03:39.5392588Z # and specifying experiments to enable in a comma-separated list. 2025-09-07T07:03:39.5393652Z # To always opt out of an experiment, prefix it with a "-". 2025-09-07T07:03:39.5394343Z # Experiments should be from the above list. 2025-09-07T07:03:39.5394742Z 2025-09-07T07:03:39.5394938Z @User1,-lf,split_build 2025-09-07T07:03:39.5395397Z @User2,lf 2025-09-07T07:03:39.5395790Z @User3,split_build 2025-09-07T07:03:39.5396212Z """ 2025-09-07T07:03:39.5396414Z 2025-09-07T07:03:39.5396597Z import json 2025-09-07T07:03:39.5396999Z import logging 2025-09-07T07:03:39.5397402Z import os 2025-09-07T07:03:39.5397797Z import random 2025-09-07T07:03:39.5398196Z import re 2025-09-07T07:03:39.5398582Z import sys 2025-09-07T07:03:39.5399003Z from argparse import ArgumentParser 2025-09-07T07:03:39.5399547Z from collections.abc import Iterable 2025-09-07T07:03:39.5400075Z from functools import cache 2025-09-07T07:03:39.5400565Z from logging import LogRecord 2025-09-07T07:03:39.5401077Z from typing import Any, NamedTuple 2025-09-07T07:03:39.5401632Z from urllib.request import Request, urlopen 2025-09-07T07:03:39.5402021Z 2025-09-07T07:03:39.5402191Z import yaml 2025-09-07T07:03:39.5402599Z from github import Auth, Github 2025-09-07T07:03:39.5403326Z from github.Issue import Issue 2025-09-07T07:03:39.5403651Z 2025-09-07T07:03:39.5403657Z 2025-09-07T07:03:39.5403892Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-09-07T07:03:39.5404593Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-09-07T07:03:39.5405495Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-09-07T07:03:39.5406066Z 2025-09-07T07:03:39.5406301Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-09-07T07:03:39.5407041Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-09-07T07:03:39.5407577Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-09-07T07:03:39.5408156Z OPT_OUT_LABEL = "no-runner-experiments" 2025-09-07T07:03:39.5408517Z 2025-09-07T07:03:39.5408735Z SETTING_EXPERIMENTS = "experiments" 2025-09-07T07:03:39.5409072Z 2025-09-07T07:03:39.5409269Z LF_FLEET_EXPERIMENT = "lf" 2025-09-07T07:03:39.5409750Z CANARY_FLEET_SUFFIX = ".c" 2025-09-07T07:03:39.5410038Z 2025-09-07T07:03:39.5410045Z 2025-09-07T07:03:39.5410248Z class Experiment(NamedTuple): 2025-09-07T07:03:39.5410767Z rollout_perc: float = ( 2025-09-07T07:03:39.5411423Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-09-07T07:03:39.5412139Z ) 2025-09-07T07:03:39.5412532Z all_branches: bool = ( 2025-09-07T07:03:39.5413408Z False # If True, the experiment is also enabled on the exception branches 2025-09-07T07:03:39.5414111Z ) 2025-09-07T07:03:39.5414507Z default: bool = ( 2025-09-07T07:03:39.5415096Z True # If True, the experiment is enabled by default for all queries 2025-09-07T07:03:39.5415750Z ) 2025-09-07T07:03:39.5415958Z 2025-09-07T07:03:39.5416158Z # Add more fields as needed 2025-09-07T07:03:39.5416465Z 2025-09-07T07:03:39.5416472Z 2025-09-07T07:03:39.5416667Z class Settings(NamedTuple): 2025-09-07T07:03:39.5417123Z """ 2025-09-07T07:03:39.5417596Z Settings for the experiments that can be opted into. 2025-09-07T07:03:39.5418190Z """ 2025-09-07T07:03:39.5418403Z 2025-09-07T07:03:39.5418632Z experiments: dict[str, Experiment] = {} 2025-09-07T07:03:39.5419003Z 2025-09-07T07:03:39.5419010Z 2025-09-07T07:03:39.5419225Z class ColorFormatter(logging.Formatter): 2025-09-07T07:03:39.5419871Z """Color codes the log messages based on the log level""" 2025-09-07T07:03:39.5420317Z 2025-09-07T07:03:39.5420497Z COLORS = { 2025-09-07T07:03:39.5420916Z "WARNING": "\033[33m", # Yellow 2025-09-07T07:03:39.5421591Z "ERROR": "\033[31m", # Red 2025-09-07T07:03:39.5422103Z "CRITICAL": "\033[31m", # Red 2025-09-07T07:03:39.5422636Z "INFO": "\033[0m", # Reset 2025-09-07T07:03:39.5423426Z "DEBUG": "\033[0m", # Reset 2025-09-07T07:03:39.5423915Z } 2025-09-07T07:03:39.5424118Z 2025-09-07T07:03:39.5424337Z def format(self, record: LogRecord) -> str: 2025-09-07T07:03:39.5425111Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-09-07T07:03:39.5425925Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-09-07T07:03:39.5426523Z return super().format(record) 2025-09-07T07:03:39.5426863Z 2025-09-07T07:03:39.5426870Z 2025-09-07T07:03:39.5427076Z handler = logging.StreamHandler() 2025-09-07T07:03:39.5427844Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-09-07T07:03:39.5428412Z 2025-09-07T07:03:39.5428669Z log = logging.getLogger(os.path.basename(__file__)) 2025-09-07T07:03:39.5429273Z log.addHandler(handler) 2025-09-07T07:03:39.5429759Z log.setLevel(logging.INFO) 2025-09-07T07:03:39.5430050Z 2025-09-07T07:03:39.5430057Z 2025-09-07T07:03:39.5430316Z def set_github_output(key: str, value: str) -> None: 2025-09-07T07:03:39.5430892Z """ 2025-09-07T07:03:39.5431423Z Defines outputs of the github action that invokes this script 2025-09-07T07:03:39.5432069Z """ 2025-09-07T07:03:39.5432453Z if not GITHUB_OUTPUT: 2025-09-07T07:03:39.5433761Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-09-07T07:03:39.5434915Z log.warning( 2025-09-07T07:03:39.5435787Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-09-07T07:03:39.5436741Z ) 2025-09-07T07:03:39.5446962Z print(f"::set-output name={key}::{value}") 2025-09-07T07:03:39.5447596Z return 2025-09-07T07:03:39.5447837Z 2025-09-07T07:03:39.5448224Z with open(GITHUB_OUTPUT, "a") as f: 2025-09-07T07:03:39.5448813Z log.info(f"Setting output: {key}='{value}'") 2025-09-07T07:03:39.5449394Z f.write(f"{key}={value}\n") 2025-09-07T07:03:39.5449726Z 2025-09-07T07:03:39.5449733Z 2025-09-07T07:03:39.5450033Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-09-07T07:03:39.5450662Z return frozenset( 2025-09-07T07:03:39.5451274Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-09-07T07:03:39.5451950Z ) 2025-09-07T07:03:39.5452149Z 2025-09-07T07:03:39.5452156Z 2025-09-07T07:03:39.5452333Z def parse_args() -> Any: 2025-09-07T07:03:39.5453171Z parser = ArgumentParser("Get dynamic rollout settings") 2025-09-07T07:03:39.5454093Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-09-07T07:03:39.5454867Z parser.add_argument( 2025-09-07T07:03:39.5455328Z "--github-issue-repo", 2025-09-07T07:03:39.5455790Z type=str, 2025-09-07T07:03:39.5456214Z required=False, 2025-09-07T07:03:39.5456676Z default="pytorch/test-infra", 2025-09-07T07:03:39.5457208Z help="GitHub repo to get the issue", 2025-09-07T07:03:39.5457722Z ) 2025-09-07T07:03:39.5458091Z parser.add_argument( 2025-09-07T07:03:39.5458558Z "--github-repo", 2025-09-07T07:03:39.5458989Z type=str, 2025-09-07T07:03:39.5459395Z required=True, 2025-09-07T07:03:39.5459862Z help="GitHub repo where CI is running", 2025-09-07T07:03:39.5460397Z ) 2025-09-07T07:03:39.5460772Z parser.add_argument( 2025-09-07T07:03:39.5461679Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-09-07T07:03:39.5462355Z ) 2025-09-07T07:03:39.5462900Z parser.add_argument( 2025-09-07T07:03:39.5463543Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-09-07T07:03:39.5464219Z ) 2025-09-07T07:03:39.5464583Z parser.add_argument( 2025-09-07T07:03:39.5465407Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-09-07T07:03:39.5466131Z ) 2025-09-07T07:03:39.5466512Z parser.add_argument( 2025-09-07T07:03:39.5467163Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-09-07T07:03:39.5467891Z ) 2025-09-07T07:03:39.5468268Z parser.add_argument( 2025-09-07T07:03:39.5468718Z "--github-ref-type", 2025-09-07T07:03:39.5469295Z type=str, 2025-09-07T07:03:39.5469699Z required=True, 2025-09-07T07:03:39.5470177Z help="Current GitHub ref type, branch or tag", 2025-09-07T07:03:39.5470727Z ) 2025-09-07T07:03:39.5471099Z parser.add_argument( 2025-09-07T07:03:39.5471559Z "--eligible-experiments", 2025-09-07T07:03:39.5472298Z type=_str_comma_separated_to_set, 2025-09-07T07:03:39.5473057Z required=False, 2025-09-07T07:03:39.5473482Z default="", 2025-09-07T07:03:39.5474330Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-09-07T07:03:39.5475264Z ) 2025-09-07T07:03:39.5475635Z parser.add_argument( 2025-09-07T07:03:39.5476096Z "--opt-out-experiments", 2025-09-07T07:03:39.5476601Z type=_str_comma_separated_to_set, 2025-09-07T07:03:39.5526934Z required=False, 2025-09-07T07:03:39.5527540Z default="", 2025-09-07T07:03:39.5527990Z help=( 2025-09-07T07:03:39.5528704Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-09-07T07:03:39.5529885Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-09-07T07:03:39.5530742Z ), 2025-09-07T07:03:39.5531118Z ) 2025-09-07T07:03:39.5531505Z parser.add_argument( 2025-09-07T07:03:39.5531969Z "--pr-number", 2025-09-07T07:03:39.5532408Z type=str, 2025-09-07T07:03:39.5532944Z required=False, 2025-09-07T07:03:39.5533420Z default="", 2025-09-07T07:03:39.5534095Z help="the optional PR number where this is run", 2025-09-07T07:03:39.5534696Z ) 2025-09-07T07:03:39.5534906Z 2025-09-07T07:03:39.5535106Z return parser.parse_args() 2025-09-07T07:03:39.5535437Z 2025-09-07T07:03:39.5535443Z 2025-09-07T07:03:39.5535859Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-09-07T07:03:39.5536654Z auth = Auth.Token(github_token) 2025-09-07T07:03:39.5537184Z return Github(auth=auth) 2025-09-07T07:03:39.5537501Z 2025-09-07T07:03:39.5537508Z 2025-09-07T07:03:39.5537970Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-09-07T07:03:39.5538808Z repo = gh.get_repo(repo) 2025-09-07T07:03:39.5539341Z return repo.get_issue(number=issue_num) 2025-09-07T07:03:39.5539721Z 2025-09-07T07:03:39.5539727Z 2025-09-07T07:03:39.5539931Z def get_potential_pr_author( 2025-09-07T07:03:39.5540594Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-09-07T07:03:39.5541309Z ) -> str: 2025-09-07T07:03:39.5541845Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-09-07T07:03:39.5542782Z # Fetch the actual username from the original PR. The PR number is 2025-09-07T07:03:39.5543551Z # embedded in the tag name: ciflow// 2025-09-07T07:03:39.5543994Z 2025-09-07T07:03:39.5544193Z gh = get_gh_client(github_token) 2025-09-07T07:03:39.5544536Z 2025-09-07T07:03:39.5544815Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-09-07T07:03:39.5545453Z split_tag = ref_name.split("/") 2025-09-07T07:03:39.5545978Z if ( 2025-09-07T07:03:39.5546385Z len(split_tag) == 3 2025-09-07T07:03:39.5546893Z and split_tag[0] == "ciflow" 2025-09-07T07:03:39.5547425Z and split_tag[2].isnumeric() 2025-09-07T07:03:39.5547948Z ): 2025-09-07T07:03:39.5548349Z pr_number = split_tag[2] 2025-09-07T07:03:39.5548998Z try: 2025-09-07T07:03:39.5549453Z repository = gh.get_repo(repo) 2025-09-07T07:03:39.5550083Z pull = repository.get_pull(number=int(pr_number)) 2025-09-07T07:03:39.5550701Z except Exception as e: 2025-09-07T07:03:39.5551243Z raise Exception( # noqa: TRY002 2025-09-07T07:03:39.5551938Z f"issue with pull request {pr_number} from repo {repository}" 2025-09-07T07:03:39.5552595Z ) from e 2025-09-07T07:03:39.5553326Z return pull.user.login # type: ignore[no-any-return] 2025-09-07T07:03:39.5554036Z # In all other cases, return the original input username 2025-09-07T07:03:39.5554635Z return username 2025-09-07T07:03:39.5554885Z 2025-09-07T07:03:39.5554892Z 2025-09-07T07:03:39.5555128Z def is_exception_branch(branch: str) -> bool: 2025-09-07T07:03:39.5555676Z """ 2025-09-07T07:03:39.5556343Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-09-07T07:03:39.5557157Z """ 2025-09-07T07:03:39.5557729Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-09-07T07:03:39.5558259Z 2025-09-07T07:03:39.5558266Z 2025-09-07T07:03:39.5558479Z def load_yaml(yaml_text: str) -> Any: 2025-09-07T07:03:39.5559000Z try: 2025-09-07T07:03:39.5559416Z data = yaml.safe_load(yaml_text) 2025-09-07T07:03:39.5559940Z return data 2025-09-07T07:03:39.5560367Z except yaml.YAMLError: 2025-09-07T07:03:39.5560880Z log.exception("Error loading YAML") 2025-09-07T07:03:39.5561414Z raise 2025-09-07T07:03:39.5561640Z 2025-09-07T07:03:39.5561647Z 2025-09-07T07:03:39.5562076Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-09-07T07:03:39.5562977Z """ 2025-09-07T07:03:39.5563631Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-09-07T07:03:39.5564248Z 2025-09-07T07:03:39.5564741Z If the issue body contains "---" then the text above that is the settings 2025-09-07T07:03:39.5565537Z and the text below is the list of opted in users. 2025-09-07T07:03:39.5565951Z 2025-09-07T07:03:39.5566337Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-09-07T07:03:39.5567067Z """ 2025-09-07T07:03:39.5567534Z rollout_state_parts = rollout_state.split("---") 2025-09-07T07:03:39.5568172Z if len(rollout_state_parts) >= 2: 2025-09-07T07:03:39.5568797Z return rollout_state_parts[0], rollout_state_parts[1] 2025-09-07T07:03:39.5569404Z else: 2025-09-07T07:03:39.5569812Z return "", rollout_state 2025-09-07T07:03:39.5570126Z 2025-09-07T07:03:39.5570133Z 2025-09-07T07:03:39.5570343Z class UserOptins(dict[str, list[str]]): 2025-09-07T07:03:39.5570882Z """ 2025-09-07T07:03:39.5571429Z Dictionary of users with a list of features they have opted into 2025-09-07T07:03:39.5572103Z """ 2025-09-07T07:03:39.5572312Z 2025-09-07T07:03:39.5572325Z 2025-09-07T07:03:39.5572808Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-09-07T07:03:39.5573489Z """ 2025-09-07T07:03:39.5574227Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-09-07T07:03:39.5574915Z 2025-09-07T07:03:39.5575544Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-09-07T07:03:39.5576551Z - Example line: "@User1,lf,split_build" 2025-09-07T07:03:39.5577271Z - A "#" prefix indicates the user is opted out of all experiments 2025-09-07T07:03:39.5577772Z 2025-09-07T07:03:39.5577779Z 2025-09-07T07:03:39.5577952Z """ 2025-09-07T07:03:39.5578358Z optins = UserOptins() 2025-09-07T07:03:39.5578864Z for user in user_optin_text.split("\n"): 2025-09-07T07:03:39.5579454Z user = user.strip("\r\n\t -") 2025-09-07T07:03:39.5580014Z if not user or not user.startswith("@"): 2025-09-07T07:03:39.5580784Z # Not a valid user. Skip 2025-09-07T07:03:39.5581281Z continue 2025-09-07T07:03:39.5581542Z 2025-09-07T07:03:39.5581714Z if user: 2025-09-07T07:03:39.5582176Z usr_name = user.split(",")[0].strip("@") 2025-09-07T07:03:39.5582994Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-09-07T07:03:39.5583501Z 2025-09-07T07:03:39.5583682Z return optins 2025-09-07T07:03:39.5583931Z 2025-09-07T07:03:39.5583938Z 2025-09-07T07:03:39.5584231Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-09-07T07:03:39.5584862Z """ 2025-09-07T07:03:39.5585283Z Check if the experiment name is valid. 2025-09-07T07:03:39.5585823Z A valid name: 2025-09-07T07:03:39.5586486Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-09-07T07:03:39.5587478Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-09-07T07:03:39.5588252Z - Cannot contain spaces 2025-09-07T07:03:39.5588739Z """ 2025-09-07T07:03:39.5588947Z 2025-09-07T07:03:39.5589228Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-09-07T07:03:39.5589948Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-09-07T07:03:39.5590415Z 2025-09-07T07:03:39.5590591Z if valid: 2025-09-07T07:03:39.5590989Z return True 2025-09-07T07:03:39.5591244Z 2025-09-07T07:03:39.5591412Z log.error( 2025-09-07T07:03:39.5593031Z 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-09-07T07:03:39.5594626Z ) 2025-09-07T07:03:39.5595018Z return False 2025-09-07T07:03:39.5595273Z 2025-09-07T07:03:39.5595280Z 2025-09-07T07:03:39.5595600Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-09-07T07:03:39.5596241Z """ 2025-09-07T07:03:39.5596994Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-09-07T07:03:39.5597755Z """ 2025-09-07T07:03:39.5598133Z try: 2025-09-07T07:03:39.5598525Z if settings_text: 2025-09-07T07:03:39.5599279Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-09-07T07:03:39.5600097Z # for easy reading 2025-09-07T07:03:39.5600899Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-09-07T07:03:39.5601812Z # the backtick character in shell commands. 2025-09-07T07:03:39.5602437Z backtick = chr(96) # backtick character 2025-09-07T07:03:39.5603244Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-09-07T07:03:39.5603940Z settings = load_yaml(settings_text) 2025-09-07T07:03:39.5604337Z 2025-09-07T07:03:39.5604763Z # For now we just load experiments. We can expand this if/when we add more settings 2025-09-07T07:03:39.5605556Z experiments = {} 2025-09-07T07:03:39.5605885Z 2025-09-07T07:03:39.5606278Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-09-07T07:03:39.5607074Z if not is_valid_experiment_name(exp_name): 2025-09-07T07:03:39.5608201Z # 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-09-07T07:03:39.5609269Z continue 2025-09-07T07:03:39.5609565Z 2025-09-07T07:03:39.5609766Z valid_settings = {} 2025-09-07T07:03:39.5610304Z for setting in exp_settings: 2025-09-07T07:03:39.5610904Z if setting not in Experiment._fields: 2025-09-07T07:03:39.5611481Z log.warning( 2025-09-07T07:03:39.5612209Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-09-07T07:03:39.5613195Z ) 2025-09-07T07:03:39.5613653Z else: 2025-09-07T07:03:39.5614184Z valid_settings[setting] = exp_settings[setting] 2025-09-07T07:03:39.5614636Z 2025-09-07T07:03:39.5614920Z experiments[exp_name] = Experiment(**valid_settings) 2025-09-07T07:03:39.5615582Z return Settings(experiments) 2025-09-07T07:03:39.5615944Z 2025-09-07T07:03:39.5616124Z except Exception: 2025-09-07T07:03:39.5616627Z log.exception("Failed to parse settings") 2025-09-07T07:03:39.5617019Z 2025-09-07T07:03:39.5617196Z return Settings() 2025-09-07T07:03:39.5617474Z 2025-09-07T07:03:39.5617481Z 2025-09-07T07:03:39.5617739Z def parse_settings(rollout_state: str) -> Settings: 2025-09-07T07:03:39.5618332Z """ 2025-09-07T07:03:39.5618792Z Parse settings, if any, from the rollout state. 2025-09-07T07:03:39.5619208Z 2025-09-07T07:03:39.5619578Z If the issue body contains "---" then the text above that is the settings 2025-09-07T07:03:39.5620360Z and the text below is the list of opted in users. 2025-09-07T07:03:39.5620776Z 2025-09-07T07:03:39.5621197Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-09-07T07:03:39.5621953Z """ 2025-09-07T07:03:39.5622532Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:03:39.5623425Z return parse_settings_from_text(settings_text) 2025-09-07T07:03:39.5623855Z 2025-09-07T07:03:39.5623862Z 2025-09-07T07:03:39.5624123Z def parse_users(rollout_state: str) -> UserOptins: 2025-09-07T07:03:39.5624711Z """ 2025-09-07T07:03:39.5625141Z Parse users from the rollout state. 2025-09-07T07:03:39.5625507Z 2025-09-07T07:03:39.5625684Z """ 2025-09-07T07:03:39.5626226Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-09-07T07:03:39.5627006Z return parse_user_opt_in_from_text(users_text) 2025-09-07T07:03:39.5627457Z 2025-09-07T07:03:39.5627469Z 2025-09-07T07:03:39.5628028Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:03:39.5628807Z """ 2025-09-07T07:03:39.5629255Z Check if a user is opted into an experiment 2025-09-07T07:03:39.5629816Z """ 2025-09-07T07:03:39.5630288Z return experiment_name in user_optins.get(user, []) 2025-09-07T07:03:39.5630716Z 2025-09-07T07:03:39.5630723Z 2025-09-07T07:03:39.5631155Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-09-07T07:03:39.5631928Z """ 2025-09-07T07:03:39.5632408Z Check if a user explicitly opted out of an experiment 2025-09-07T07:03:39.5633173Z """ 2025-09-07T07:03:39.5633711Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-09-07T07:03:39.5634425Z experiment_optout = "-" + experiment_name 2025-09-07T07:03:39.5635088Z if experiment_optout not in user_optins.get(user, []): 2025-09-07T07:03:39.5635719Z return False 2025-09-07T07:03:39.5635995Z 2025-09-07T07:03:39.5636285Z if is_user_opted_in(user, user_optins, experiment_name): 2025-09-07T07:03:39.5636913Z log.warning( 2025-09-07T07:03:39.5637742Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-09-07T07:03:39.5638663Z ) 2025-09-07T07:03:39.5638884Z 2025-09-07T07:03:39.5639065Z return True 2025-09-07T07:03:39.5639313Z 2025-09-07T07:03:39.5639319Z 2025-09-07T07:03:39.5639517Z def get_runner_prefix( 2025-09-07T07:03:39.5639980Z rollout_state: str, 2025-09-07T07:03:39.5640477Z workflow_requestors: Iterable[str], 2025-09-07T07:03:39.5641023Z branch: str, 2025-09-07T07:03:39.5641548Z eligible_experiments: frozenset[str] = frozenset(), 2025-09-07T07:03:39.5642237Z opt_out_experiments: frozenset[str] = frozenset(), 2025-09-07T07:03:39.5642975Z is_canary: bool = False, 2025-09-07T07:03:39.5643457Z ) -> str: 2025-09-07T07:03:39.5643898Z settings = parse_settings(rollout_state) 2025-09-07T07:03:39.5644649Z user_optins = parse_users(rollout_state) 2025-09-07T07:03:39.5645038Z 2025-09-07T07:03:39.5645222Z fleet_prefix = "" 2025-09-07T07:03:39.5645673Z prefixes = [] 2025-09-07T07:03:39.5646326Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-09-07T07:03:39.5647307Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-09-07T07:03:39.5648037Z log.info( 2025-09-07T07:03:39.5648754Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-09-07T07:03:39.5649533Z ) 2025-09-07T07:03:39.5649944Z continue 2025-09-07T07:03:39.5650206Z 2025-09-07T07:03:39.5650412Z if opt_out_experiments: 2025-09-07T07:03:39.5650974Z if experiment_name in opt_out_experiments: 2025-09-07T07:03:39.5651643Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-09-07T07:03:39.5652262Z log.info( 2025-09-07T07:03:39.5653331Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-09-07T07:03:39.5654351Z ) 2025-09-07T07:03:39.5654770Z continue 2025-09-07T07:03:39.5655048Z 2025-09-07T07:03:39.5655253Z if eligible_experiments: 2025-09-07T07:03:39.5655866Z if experiment_name not in eligible_experiments: 2025-09-07T07:03:39.5656535Z exp_list = ", ".join(eligible_experiments) 2025-09-07T07:03:39.5657117Z log.info( 2025-09-07T07:03:39.5657923Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-09-07T07:03:39.5658787Z ) 2025-09-07T07:03:39.5659205Z continue 2025-09-07T07:03:39.5659701Z elif not experiment_settings.default: 2025-09-07T07:03:39.5660257Z log.info( 2025-09-07T07:03:39.5661102Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-09-07T07:03:39.5661875Z ) 2025-09-07T07:03:39.5662276Z continue 2025-09-07T07:03:39.5662542Z 2025-09-07T07:03:39.5662942Z # Is any workflow_requestor opted out to this experiment? 2025-09-07T07:03:39.5663588Z opted_out_users = [ 2025-09-07T07:03:39.5664067Z requestor 2025-09-07T07:03:39.5664548Z for requestor in workflow_requestors 2025-09-07T07:03:39.5665247Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-09-07T07:03:39.5665895Z ] 2025-09-07T07:03:39.5666117Z 2025-09-07T07:03:39.5666312Z if opted_out_users: 2025-09-07T07:03:39.5666787Z log.info( 2025-09-07T07:03:39.5667432Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-09-07T07:03:39.5668144Z ) 2025-09-07T07:03:39.5668538Z continue 2025-09-07T07:03:39.5668809Z 2025-09-07T07:03:39.5669105Z # Is any workflow_requestor opted in to this experiment? 2025-09-07T07:03:39.5669792Z opted_in_users = [ 2025-09-07T07:03:39.5670274Z requestor 2025-09-07T07:03:39.5670752Z for requestor in workflow_requestors 2025-09-07T07:03:39.5671443Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-09-07T07:03:39.5672084Z ] 2025-09-07T07:03:39.5672305Z 2025-09-07T07:03:39.5672488Z enabled = False 2025-09-07T07:03:39.5673110Z if opted_in_users: 2025-09-07T07:03:39.5673577Z log.info( 2025-09-07T07:03:39.5674212Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-09-07T07:03:39.5674918Z ) 2025-09-07T07:03:39.5675332Z enabled = True 2025-09-07T07:03:39.5675628Z 2025-09-07T07:03:39.5675860Z elif experiment_settings.rollout_perc: 2025-09-07T07:03:39.5676739Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-09-07T07:03:39.5677862Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-09-07T07:03:39.5678550Z log.info( 2025-09-07T07:03:39.5679448Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-09-07T07:03:39.5680389Z ) 2025-09-07T07:03:39.5680831Z enabled = True 2025-09-07T07:03:39.5681138Z 2025-09-07T07:03:39.5681315Z if enabled: 2025-09-07T07:03:39.5681768Z label = experiment_name 2025-09-07T07:03:39.5682355Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-09-07T07:03:39.5683329Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-09-07T07:03:39.5684266Z # - If it's enabled, then we always list it's prefix first 2025-09-07T07:03:39.5685068Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-09-07T07:03:39.5685762Z if is_canary: 2025-09-07T07:03:39.5686283Z label += CANARY_FLEET_SUFFIX 2025-09-07T07:03:39.5686872Z fleet_prefix = label 2025-09-07T07:03:39.5687391Z else: 2025-09-07T07:03:39.5687846Z prefixes.append(label) 2025-09-07T07:03:39.5688206Z 2025-09-07T07:03:39.5688405Z if len(prefixes) > 1: 2025-09-07T07:03:39.5688870Z log.error( 2025-09-07T07:03:39.5689928Z 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-09-07T07:03:39.5691072Z ) 2025-09-07T07:03:39.5691504Z prefixes = prefixes[:1] 2025-09-07T07:03:39.5691831Z 2025-09-07T07:03:39.5692032Z # Fleet always comes first 2025-09-07T07:03:39.5692531Z if fleet_prefix: 2025-09-07T07:03:39.5693108Z prefixes.insert(0, fleet_prefix) 2025-09-07T07:03:39.5693496Z 2025-09-07T07:03:39.5693889Z return ".".join(prefixes) + "." if prefixes else "" 2025-09-07T07:03:39.5694340Z 2025-09-07T07:03:39.5694347Z 2025-09-07T07:03:39.5694809Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-09-07T07:03:39.5695608Z """ 2025-09-07T07:03:39.5696220Z Gets the first comment of the issue, which contains the desired rollout state. 2025-09-07T07:03:39.5696799Z 2025-09-07T07:03:39.5697199Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-09-07T07:03:39.5697931Z """ 2025-09-07T07:03:39.5698346Z gh = get_gh_client(github_token) 2025-09-07T07:03:39.5698909Z issue = get_issue(gh, repo, issue_num) 2025-09-07T07:03:39.5699568Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-09-07T07:03:39.5700030Z 2025-09-07T07:03:39.5700037Z 2025-09-07T07:03:39.5700454Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-09-07T07:03:39.5701256Z for _ in range(num_retries): 2025-09-07T07:03:39.5701752Z try: 2025-09-07T07:03:39.5702217Z req = Request(url=url, headers=headers) 2025-09-07T07:03:39.5703021Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-09-07T07:03:39.5703683Z return json.loads(content) 2025-09-07T07:03:39.5704240Z except Exception as e: 2025-09-07T07:03:39.5704806Z log.warning(f"Could not download {url}: {e}") 2025-09-07T07:03:39.5705226Z 2025-09-07T07:03:39.5705616Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-09-07T07:03:39.5706356Z return {} 2025-09-07T07:03:39.5706602Z 2025-09-07T07:03:39.5706608Z 2025-09-07T07:03:39.5706777Z @cache 2025-09-07T07:03:39.5707427Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-09-07T07:03:39.5708208Z """ 2025-09-07T07:03:39.5708625Z Dynamically get PR information 2025-09-07T07:03:39.5709140Z """ 2025-09-07T07:03:39.5709807Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-09-07T07:03:39.5710454Z headers = { 2025-09-07T07:03:39.5710941Z "Accept": "application/vnd.github.v3+json", 2025-09-07T07:03:39.5711569Z "Authorization": f"token {github_token}", 2025-09-07T07:03:39.5712127Z } 2025-09-07T07:03:39.5712576Z json_response: dict[str, Any] = download_json( 2025-09-07T07:03:39.5713382Z url=f"{github_api}/issues/{pr_number}", 2025-09-07T07:03:39.5713963Z headers=headers, 2025-09-07T07:03:39.5714424Z ) 2025-09-07T07:03:39.5714631Z 2025-09-07T07:03:39.5714833Z if not json_response: 2025-09-07T07:03:39.5715438Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-09-07T07:03:39.5716100Z return {} 2025-09-07T07:03:39.5716348Z 2025-09-07T07:03:39.5716535Z return json_response 2025-09-07T07:03:39.5716829Z 2025-09-07T07:03:39.5716835Z 2025-09-07T07:03:39.5717254Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-09-07T07:03:39.5718020Z """ 2025-09-07T07:03:39.5718572Z Dynamically get the latest list of labels from the pull request 2025-09-07T07:03:39.5719269Z """ 2025-09-07T07:03:39.5719778Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-09-07T07:03:39.5720425Z return { 2025-09-07T07:03:39.5721047Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-09-07T07:03:39.5721785Z } 2025-09-07T07:03:39.5721996Z 2025-09-07T07:03:39.5722003Z 2025-09-07T07:03:39.5722189Z def main() -> None: 2025-09-07T07:03:39.5722634Z args = parse_args() 2025-09-07T07:03:39.5723411Z 2025-09-07T07:03:39.5723663Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-09-07T07:03:39.5724066Z 2025-09-07T07:03:39.5724277Z # Check if the PR is opt-out 2025-09-07T07:03:39.5724795Z if args.pr_number: 2025-09-07T07:03:39.5725474Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-09-07T07:03:39.5726402Z if OPT_OUT_LABEL in labels: 2025-09-07T07:03:39.5726939Z log.info( 2025-09-07T07:03:39.5727697Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-09-07T07:03:39.5728482Z ) 2025-09-07T07:03:39.5729057Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:03:39.5729758Z sys.exit() 2025-09-07T07:03:39.5730034Z 2025-09-07T07:03:39.5730208Z try: 2025-09-07T07:03:39.5730672Z rollout_state = get_rollout_state_from_issue( 2025-09-07T07:03:39.5731401Z args.github_token, args.github_issue_repo, args.github_issue 2025-09-07T07:03:39.5732058Z ) 2025-09-07T07:03:39.5732279Z 2025-09-07T07:03:39.5732501Z username = get_potential_pr_author( 2025-09-07T07:03:39.5733186Z args.github_token, 2025-09-07T07:03:39.5733687Z args.github_repo, 2025-09-07T07:03:39.5734185Z args.github_actor, 2025-09-07T07:03:39.5734694Z args.github_ref_type, 2025-09-07T07:03:39.5735214Z args.github_branch, 2025-09-07T07:03:39.5735703Z ) 2025-09-07T07:03:39.5735923Z 2025-09-07T07:03:39.5736221Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-09-07T07:03:39.5736691Z 2025-09-07T07:03:39.5736919Z runner_label_prefix = get_runner_prefix( 2025-09-07T07:03:39.5737492Z rollout_state, 2025-09-07T07:03:39.5737999Z (args.github_issue_owner, username), 2025-09-07T07:03:39.5738566Z args.github_branch, 2025-09-07T07:03:39.5739079Z args.eligible_experiments, 2025-09-07T07:03:39.5739640Z args.opt_out_experiments, 2025-09-07T07:03:39.5740165Z is_canary, 2025-09-07T07:03:39.5740599Z ) 2025-09-07T07:03:39.5740816Z 2025-09-07T07:03:39.5741012Z except Exception as e: 2025-09-07T07:03:39.5741488Z log.error( 2025-09-07T07:03:39.5742170Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-09-07T07:03:39.5743212Z ) 2025-09-07T07:03:39.5743427Z 2025-09-07T07:03:39.5743781Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-09-07T07:03:39.5744286Z 2025-09-07T07:03:39.5744292Z 2025-09-07T07:03:39.5744482Z if __name__ == "__main__": 2025-09-07T07:03:39.5744948Z main() 2025-09-07T07:03:39.5745171Z 2025-09-07T07:03:39.5848568Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T07:03:39.5850123Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-09-07T07:03:39.5891371Z shell: /usr/bin/bash -e {0} 2025-09-07T07:03:39.5892170Z env: 2025-09-07T07:03:39.5893544Z GITHUB_TOKEN: *** 2025-09-07T07:03:39.5894430Z ISSUE_NUMBER: 5132 2025-09-07T07:03:39.5895289Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:03:39.5896263Z ISSUE_OWNER: 2025-09-07T07:03:39.5897001Z CHECK_EXPERIMENTS: 2025-09-07T07:03:39.5897800Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:03:39.5898596Z PR_NUMBER: 2025-09-07T07:03:39.5899298Z ##[endgroup] 2025-09-07T07:03:41.5538668Z Defaulting to user installation because normal site-packages is not writeable 2025-09-07T07:03:42.7103120Z Collecting urllib3==1.26.18 2025-09-07T07:03:42.7896568Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-09-07T07:03:42.8226539Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 2.0 MB/s eta 0:00:00 2025-09-07T07:03:42.8652360Z Collecting PyGithub==2.3.0 2025-09-07T07:03:42.8838932Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-09-07T07:03:42.9445763Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-09-07T07:03:42.9632028Z 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-09-07T07:03:42.9678249Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-09-07T07:03:42.9691196Z 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-09-07T07:03:42.9712328Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-09-07T07:03:43.0130021Z Collecting Deprecated (from PyGithub==2.3.0) 2025-09-07T07:03:43.0323923Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-09-07T07:03:43.0558992Z 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-09-07T07:03:43.2179589Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T07:03:43.2366138Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-09-07T07:03:43.3757677Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-09-07T07:03:43.3944970Z 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-09-07T07:03:43.4279298Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-09-07T07:03:43.4463648Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-09-07T07:03:43.4842473Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-09-07T07:03:43.5073826Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 6.3 MB/s eta 0:00:00 2025-09-07T07:03:43.5257898Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-09-07T07:03:43.5509492Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 15.9 MB/s eta 0:00:00 2025-09-07T07:03:43.5695827Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-09-07T07:03:43.5931540Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 39.0 MB/s eta 0:00:00 2025-09-07T07:03:43.6123306Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-09-07T07:03:43.6334052Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-09-07T07:03:43.6400583Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 100.4 MB/s eta 0:00:00 2025-09-07T07:03:43.6587020Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-09-07T07:03:43.6630273Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 28.3 MB/s eta 0:00:00 2025-09-07T07:03:43.6815257Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-09-07T07:03:43.6858258Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 40.3 MB/s eta 0:00:00 2025-09-07T07:03:44.0201726Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-09-07T07:03:44.5867241Z 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-09-07T07:03:44.7765685Z ##[group]Run curr_branch="main" 2025-09-07T07:03:44.7765998Z curr_branch="main" 2025-09-07T07:03:44.7766214Z curr_ref_type="branch" 2025-09-07T07:03:44.7766495Z echo "Current branch is '$curr_branch'" 2025-09-07T07:03:44.7766743Z  2025-09-07T07:03:44.7766933Z python3 runner_determinator.py \ 2025-09-07T07:03:44.7767213Z  --github-token "$GITHUB_TOKEN" \ 2025-09-07T07:03:44.7767489Z  --github-issue "$ISSUE_NUMBER" \ 2025-09-07T07:03:44.7767745Z  --github-branch "$curr_branch" \ 2025-09-07T07:03:44.7768005Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-09-07T07:03:44.7768287Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-09-07T07:03:44.7768559Z  --github-ref-type "$curr_ref_type" \ 2025-09-07T07:03:44.7768829Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-09-07T07:03:44.7769124Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-09-07T07:03:44.7769497Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-09-07T07:03:44.7769786Z  --pr-number "${PR_NUMBER}" 2025-09-07T07:03:44.7801845Z shell: /usr/bin/bash -e {0} 2025-09-07T07:03:44.7802079Z env: 2025-09-07T07:03:44.7802670Z GITHUB_TOKEN: *** 2025-09-07T07:03:44.7803086Z ISSUE_NUMBER: 5132 2025-09-07T07:03:44.7803292Z TRIGGERING_ACTOR: pytorchmergebot 2025-09-07T07:03:44.7803524Z ISSUE_OWNER: 2025-09-07T07:03:44.7803707Z CHECK_EXPERIMENTS: 2025-09-07T07:03:44.7803905Z OPT_OUT_EXPERIMENTS: lf 2025-09-07T07:03:44.7804103Z PR_NUMBER: 2025-09-07T07:03:44.7804267Z ##[endgroup] 2025-09-07T07:03:44.7856788Z Current branch is 'main' 2025-09-07T07:03:46.4425682Z INFO : Skipping experiment 'lf', as this workflow has opted-out (opted out experiments are: lf) 2025-09-07T07:03:46.4427279Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-09-07T07:03:46.4428584Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-09-07T07:03:46.4429845Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-09-07T07:03:46.4430858Z INFO : Setting output: label-type='' 2025-09-07T07:03:46.4783393Z Evaluate and set job outputs 2025-09-07T07:03:46.4790145Z Cleaning up orphan processes