2025-12-04T08:44:53.2298397Z Current runner version: '2.329.0' 2025-12-04T08:44:53.2321202Z ##[group]Runner Image Provisioner 2025-12-04T08:44:53.2322052Z Hosted Compute Agent 2025-12-04T08:44:53.2322730Z Version: 20251124.448 2025-12-04T08:44:53.2323387Z Commit: fda5086b43ec66ade217e5fcd18146c879571177 2025-12-04T08:44:53.2324129Z Build Date: 2025-11-24T21:16:26Z 2025-12-04T08:44:53.2324829Z ##[endgroup] 2025-12-04T08:44:53.2325356Z ##[group]Operating System 2025-12-04T08:44:53.2325957Z Ubuntu 2025-12-04T08:44:53.2326486Z 24.04.3 2025-12-04T08:44:53.2326999Z LTS 2025-12-04T08:44:53.2327497Z ##[endgroup] 2025-12-04T08:44:53.2328063Z ##[group]Runner Image 2025-12-04T08:44:53.2328784Z Image: ubuntu-24.04 2025-12-04T08:44:53.2329288Z Version: 20251126.144.1 2025-12-04T08:44:53.2330770Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251126.144/images/ubuntu/Ubuntu2404-Readme.md 2025-12-04T08:44:53.2332441Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251126.144 2025-12-04T08:44:53.2333518Z ##[endgroup] 2025-12-04T08:44:53.2334622Z ##[group]GITHUB_TOKEN Permissions 2025-12-04T08:44:53.2336836Z Contents: read 2025-12-04T08:44:53.2337542Z Metadata: read 2025-12-04T08:44:53.2338059Z ##[endgroup] 2025-12-04T08:44:53.2340258Z Secret source: Actions 2025-12-04T08:44:53.2341081Z Prepare workflow directory 2025-12-04T08:44:53.2858143Z Prepare all required actions 2025-12-04T08:44:53.2912748Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (ffd9b0fb4355e97af82fc42cf185c3ffa0fc0a32) 2025-12-04T08:44:53.2917628Z ##[group] Inputs 2025-12-04T08:44:53.2918254Z check_experiments: 2025-12-04T08:44:53.2918979Z opt_out_experiments: 2025-12-04T08:44:53.2919846Z triggering_actor: huydhn 2025-12-04T08:44:53.2920474Z issue_owner: 2025-12-04T08:44:53.2921115Z curr_branch: main 2025-12-04T08:44:53.2921678Z curr_ref_type: branch 2025-12-04T08:44:53.2922372Z issue_number: 5132 2025-12-04T08:44:53.2922997Z ##[endgroup] 2025-12-04T08:44:53.2923740Z Complete job name: before-test / get-label-type / runner-determinator 2025-12-04T08:44:54.0128283Z ##[group]Run cat < runner_determinator.py 2025-12-04T08:44:54.0131082Z cat < runner_determinator.py 2025-12-04T08:44:54.0131780Z # flake8: noqa: G004 2025-12-04T08:44:54.0132363Z  2025-12-04T08:44:54.0133184Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:44:54.0134317Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:44:54.0135589Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:44:54.0136347Z  2025-12-04T08:44:54.0136841Z """ 2025-12-04T08:44:54.0137558Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:44:54.0138653Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:44:54.0140321Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:44:54.0141507Z of which runners should be used to run which job. 2025-12-04T08:44:54.0142320Z  2025-12-04T08:44:54.0143041Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:44:54.0144140Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:44:54.0145289Z settings are considered to be empty with only the second part, the user 2025-12-04T08:44:54.0146187Z list, defined. 2025-12-04T08:44:54.0146698Z  2025-12-04T08:44:54.0147477Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:44:54.0148588Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:44:54.0149701Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:44:54.0150538Z  2025-12-04T08:44:54.0151541Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:44:54.0152631Z The user list is also a comma separated list of additional features or 2025-12-04T08:44:54.0153633Z experiments which the user could be opted in to. 2025-12-04T08:44:54.0154317Z  2025-12-04T08:44:54.0154841Z The user list has the following rules: 2025-12-04T08:44:54.0155501Z  2025-12-04T08:44:54.0156263Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:44:54.0157327Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:44:54.0158280Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:44:54.0158991Z  2025-12-04T08:44:54.0159551Z Example config: 2025-12-04T08:44:54.0160252Z  # A list of experiments that can be opted into. 2025-12-04T08:44:54.0161118Z  # This defines the behavior they'll induce when opted into. 2025-12-04T08:44:54.0161923Z  # Expected syntax is: 2025-12-04T08:44:54.0162805Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:44:54.0163952Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:44:54.0164861Z  2025-12-04T08:44:54.0165300Z  experiments: 2025-12-04T08:44:54.0165945Z  lf: 2025-12-04T08:44:54.0166484Z  rollout_percent: 25 2025-12-04T08:44:54.0167055Z  all_branches: false 2025-12-04T08:44:54.0167752Z  default: true 2025-12-04T08:44:54.0168295Z  --- 2025-12-04T08:44:54.0168772Z  2025-12-04T08:44:54.0169327Z  # Opt-ins: 2025-12-04T08:44:54.0170234Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:44:54.0171458Z  # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:44:54.0172517Z  # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:44:54.0173399Z  # Experiments should be from the above list. 2025-12-04T08:44:54.0174097Z  2025-12-04T08:44:54.0174669Z  @User1,-lf,split_build 2025-12-04T08:44:54.0175262Z  @User2,lf 2025-12-04T08:44:54.0175848Z  @User3,split_build 2025-12-04T08:44:54.0176494Z """ 2025-12-04T08:44:54.0176961Z  2025-12-04T08:44:54.0177483Z import json 2025-12-04T08:44:54.0260268Z import logging 2025-12-04T08:44:54.0260908Z import os 2025-12-04T08:44:54.0261358Z import random 2025-12-04T08:44:54.0261837Z import re 2025-12-04T08:44:54.0262275Z import sys 2025-12-04T08:44:54.0262761Z from argparse import ArgumentParser 2025-12-04T08:44:54.0263451Z from collections.abc import Iterable 2025-12-04T08:44:54.0264079Z from functools import cache 2025-12-04T08:44:54.0264654Z from logging import LogRecord 2025-12-04T08:44:54.0265248Z from typing import Any, NamedTuple 2025-12-04T08:44:54.0265878Z from urllib.request import Request, urlopen 2025-12-04T08:44:54.0266500Z  2025-12-04T08:44:54.0266888Z import yaml 2025-12-04T08:44:54.0267363Z from github import Auth, Github 2025-12-04T08:44:54.0267943Z from github.Issue import Issue 2025-12-04T08:44:54.0268487Z  2025-12-04T08:44:54.0268867Z  2025-12-04T08:44:54.0269333Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:44:54.0270477Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:44:54.0271500Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:44:54.0272274Z  2025-12-04T08:44:54.0273024Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:44:54.0273675Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:44:54.0274285Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:44:54.0274961Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:44:54.0275545Z  2025-12-04T08:44:54.0275981Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:44:54.0276539Z  2025-12-04T08:44:54.0276949Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:44:54.0277495Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:44:54.0278007Z  2025-12-04T08:44:54.0278379Z  2025-12-04T08:44:54.0278799Z class Experiment(NamedTuple): 2025-12-04T08:44:54.0279472Z  rollout_perc: float = ( 2025-12-04T08:44:54.0280235Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:44:54.0280974Z  ) 2025-12-04T08:44:54.0281403Z  all_branches: bool = ( 2025-12-04T08:44:54.0282142Z  False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:44:54.0282878Z  ) 2025-12-04T08:44:54.0283296Z  default: bool = ( 2025-12-04T08:44:54.0283970Z  True # If True, the experiment is enabled by default for all queries 2025-12-04T08:44:54.0284673Z  ) 2025-12-04T08:44:54.0285062Z  2025-12-04T08:44:54.0285479Z  # Add more fields as needed 2025-12-04T08:44:54.0286011Z  2025-12-04T08:44:54.0286387Z  2025-12-04T08:44:54.0286806Z class Settings(NamedTuple): 2025-12-04T08:44:54.0287324Z  """ 2025-12-04T08:44:54.0287879Z  Settings for the experiments that can be opted into. 2025-12-04T08:44:54.0288525Z  """ 2025-12-04T08:44:54.0288929Z  2025-12-04T08:44:54.0289466Z  experiments: dict[str, Experiment] = {} 2025-12-04T08:44:54.0290066Z  2025-12-04T08:44:54.0290573Z  2025-12-04T08:44:54.0291037Z class ColorFormatter(logging.Formatter): 2025-12-04T08:44:54.0291753Z  """Color codes the log messages based on the log level""" 2025-12-04T08:44:54.0292403Z  2025-12-04T08:44:54.0292788Z  COLORS = { 2025-12-04T08:44:54.0293273Z  "WARNING": "\033[33m", # Yellow 2025-12-04T08:44:54.0293848Z  "ERROR": "\033[31m", # Red 2025-12-04T08:44:54.0294404Z  "CRITICAL": "\033[31m", # Red 2025-12-04T08:44:54.0294973Z  "INFO": "\033[0m", # Reset 2025-12-04T08:44:54.0295545Z  "DEBUG": "\033[0m", # Reset 2025-12-04T08:44:54.0296081Z  } 2025-12-04T08:44:54.0296478Z  2025-12-04T08:44:54.0296948Z  def format(self, record: LogRecord) -> str: 2025-12-04T08:44:54.0297786Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:44:54.0298645Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:44:54.0299296Z  return super().format(record) 2025-12-04T08:44:54.0299948Z  2025-12-04T08:44:54.0300325Z  2025-12-04T08:44:54.0300749Z handler = logging.StreamHandler() 2025-12-04T08:44:54.0301567Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:44:54.0302356Z  2025-12-04T08:44:54.0302855Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:44:54.0303520Z log.addHandler(handler) 2025-12-04T08:44:54.0304044Z log.setLevel(logging.INFO) 2025-12-04T08:44:54.0304557Z  2025-12-04T08:44:54.0304918Z  2025-12-04T08:44:54.0305429Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:44:54.0306060Z  """ 2025-12-04T08:44:54.0306658Z  Defines outputs of the github action that invokes this script 2025-12-04T08:44:54.0307492Z  """ 2025-12-04T08:44:54.0307919Z  if not GITHUB_OUTPUT: 2025-12-04T08:44:54.0309105Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:44:54.0310675Z  log.warning( 2025-12-04T08:44:54.0311671Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:44:54.0312667Z  ) 2025-12-04T08:44:54.0313169Z  print(f"::set-output name={key}::{value}") 2025-12-04T08:44:54.0313785Z  return 2025-12-04T08:44:54.0314219Z  2025-12-04T08:44:54.0314664Z  with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:44:54.0315318Z  log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:44:54.0315949Z  f.write(f"{key}={value}\n") 2025-12-04T08:44:54.0316487Z  2025-12-04T08:44:54.0316855Z  2025-12-04T08:44:54.0317417Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:44:54.0318146Z  return frozenset( 2025-12-04T08:44:54.0318866Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:44:54.0319722Z  ) 2025-12-04T08:44:54.0320155Z  2025-12-04T08:44:54.0320531Z  2025-12-04T08:44:54.0320939Z def parse_args() -> Any: 2025-12-04T08:44:54.0321617Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:44:54.0322574Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:44:54.0323416Z  parser.add_argument( 2025-12-04T08:44:54.0323968Z  "--github-issue-repo", 2025-12-04T08:44:54.0324554Z  type=str, 2025-12-04T08:44:54.0325040Z  required=False, 2025-12-04T08:44:54.0325729Z  default="pytorch/test-infra", 2025-12-04T08:44:54.0326385Z  help="GitHub repo to get the issue", 2025-12-04T08:44:54.0326966Z  ) 2025-12-04T08:44:54.0327406Z  parser.add_argument( 2025-12-04T08:44:54.0327942Z  "--github-repo", 2025-12-04T08:44:54.0328467Z  type=str, 2025-12-04T08:44:54.0328955Z  required=True, 2025-12-04T08:44:54.0329725Z  help="GitHub repo where CI is running", 2025-12-04T08:44:54.0330331Z  ) 2025-12-04T08:44:54.0330768Z  parser.add_argument( 2025-12-04T08:44:54.0331489Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:44:54.0332217Z  ) 2025-12-04T08:44:54.0332648Z  parser.add_argument( 2025-12-04T08:44:54.0333381Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:44:54.0334151Z  ) 2025-12-04T08:44:54.0334582Z  parser.add_argument( 2025-12-04T08:44:54.0335337Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:44:54.0336102Z  ) 2025-12-04T08:44:54.0336534Z  parser.add_argument( 2025-12-04T08:44:54.0337311Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:44:54.0338080Z  ) 2025-12-04T08:44:54.0338533Z  parser.add_argument( 2025-12-04T08:44:54.0339071Z  "--github-ref-type", 2025-12-04T08:44:54.0339720Z  type=str, 2025-12-04T08:44:54.0340206Z  required=True, 2025-12-04T08:44:54.0340828Z  help="Current GitHub ref type, branch or tag", 2025-12-04T08:44:54.0341442Z  ) 2025-12-04T08:44:54.0341875Z  parser.add_argument( 2025-12-04T08:44:54.0342573Z  "--eligible-experiments", 2025-12-04T08:44:54.0343189Z  type=_str_comma_separated_to_set, 2025-12-04T08:44:54.0343778Z  required=False, 2025-12-04T08:44:54.0344290Z  default="", 2025-12-04T08:44:54.0345256Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:44:54.0346255Z  ) 2025-12-04T08:44:54.0346689Z  parser.add_argument( 2025-12-04T08:44:54.0347235Z  "--opt-out-experiments", 2025-12-04T08:44:54.0347846Z  type=_str_comma_separated_to_set, 2025-12-04T08:44:54.0348437Z  required=False, 2025-12-04T08:44:54.0348952Z  default="", 2025-12-04T08:44:54.0349803Z  help=( 2025-12-04T08:44:54.0350616Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:44:54.0351858Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:44:54.0352753Z  ), 2025-12-04T08:44:54.0353178Z  ) 2025-12-04T08:44:54.0353609Z  parser.add_argument( 2025-12-04T08:44:54.0354144Z  "--pr-number", 2025-12-04T08:44:54.0354654Z  type=str, 2025-12-04T08:44:54.0355143Z  required=False, 2025-12-04T08:44:54.0355658Z  default="", 2025-12-04T08:44:54.0356230Z  help="the optional PR number where this is run", 2025-12-04T08:44:54.0356851Z  ) 2025-12-04T08:44:54.0357251Z  2025-12-04T08:44:54.0357671Z  return parser.parse_args() 2025-12-04T08:44:54.0358201Z  2025-12-04T08:44:54.0358575Z  2025-12-04T08:44:54.0359239Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:44:54.0360743Z  auth = Auth.Token(github_token) 2025-12-04T08:44:54.0361392Z  return Github(auth=auth) 2025-12-04T08:44:54.0361919Z  2025-12-04T08:44:54.0362292Z  2025-12-04T08:44:54.0363018Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:44:54.0363903Z  repo = gh.get_repo(repo) 2025-12-04T08:44:54.0364508Z  return repo.get_issue(number=issue_num) 2025-12-04T08:44:54.0365104Z  2025-12-04T08:44:54.0365485Z  2025-12-04T08:44:54.0365899Z def get_potential_pr_author( 2025-12-04T08:44:54.0366659Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:44:54.0367413Z ) -> str: 2025-12-04T08:44:54.0368032Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:44:54.0368927Z  # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:44:54.0369925Z  # embedded in the tag name: ciflow// 2025-12-04T08:44:54.0370566Z  2025-12-04T08:44:54.0370994Z  gh = get_gh_client(github_token) 2025-12-04T08:44:54.0371550Z  2025-12-04T08:44:54.0372077Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:44:54.0372792Z  split_tag = ref_name.split("/") 2025-12-04T08:44:54.0373352Z  if ( 2025-12-04T08:44:54.0373822Z  len(split_tag) == 3 2025-12-04T08:44:54.0374398Z  and split_tag[0] == "ciflow" 2025-12-04T08:44:54.0374988Z  and split_tag[2].isnumeric() 2025-12-04T08:44:54.0375549Z  ): 2025-12-04T08:44:54.0376017Z  pr_number = split_tag[2] 2025-12-04T08:44:54.0376573Z  try: 2025-12-04T08:44:54.0377092Z  repository = gh.get_repo(repo) 2025-12-04T08:44:54.0377955Z  pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:44:54.0378641Z  except Exception as e: 2025-12-04T08:44:54.0379245Z  raise Exception( # noqa: TRY002 2025-12-04T08:44:54.0380126Z  f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:44:54.0380839Z  ) from e 2025-12-04T08:44:54.0381494Z  return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:44:54.0382289Z  # In all other cases, return the original input username 2025-12-04T08:44:54.0382949Z  return username 2025-12-04T08:44:54.0383432Z  2025-12-04T08:44:54.0383812Z  2025-12-04T08:44:54.0384293Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:44:54.0384886Z  """ 2025-12-04T08:44:54.0385622Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:44:54.0386464Z  """ 2025-12-04T08:44:54.0387094Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:44:54.0387824Z  2025-12-04T08:44:54.0388198Z  2025-12-04T08:44:54.0388643Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:44:54.0389200Z  try: 2025-12-04T08:44:54.0389761Z  data = yaml.safe_load(yaml_text) 2025-12-04T08:44:54.0390337Z  return data 2025-12-04T08:44:54.0390850Z  except yaml.YAMLError: 2025-12-04T08:44:54.0391424Z  log.exception("Error loading YAML") 2025-12-04T08:44:54.0392002Z  raise 2025-12-04T08:44:54.0392454Z  2025-12-04T08:44:54.0392826Z  2025-12-04T08:44:54.0393502Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:44:54.0394307Z  """ 2025-12-04T08:44:54.0395146Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:44:54.0395961Z  2025-12-04T08:44:54.0396557Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:44:54.0397422Z  and the text below is the list of opted in users. 2025-12-04T08:44:54.0398042Z  2025-12-04T08:44:54.0398675Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:44:54.0399530Z  """ 2025-12-04T08:44:54.0400064Z  rollout_state_parts = rollout_state.split("---") 2025-12-04T08:44:54.0400737Z  if len(rollout_state_parts) >= 2: 2025-12-04T08:44:54.0401436Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:44:54.0402090Z  else: 2025-12-04T08:44:54.0402533Z  return "", rollout_state 2025-12-04T08:44:54.0403063Z  2025-12-04T08:44:54.0403438Z  2025-12-04T08:44:54.0404134Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:44:54.0404703Z  """ 2025-12-04T08:44:54.0405302Z  Dictionary of users with a list of features they have opted into 2025-12-04T08:44:54.0405998Z  """ 2025-12-04T08:44:54.0406403Z  2025-12-04T08:44:54.0406773Z  2025-12-04T08:44:54.0407363Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:44:54.0408085Z  """ 2025-12-04T08:44:54.0408882Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T08:44:54.0409873Z  2025-12-04T08:44:54.0410744Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:44:54.0411828Z  - Example line: "@User1,lf,split_build" 2025-12-04T08:44:54.0412739Z  - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:44:54.0413427Z  2025-12-04T08:44:54.0413795Z  2025-12-04T08:44:54.0414164Z  """ 2025-12-04T08:44:54.0414595Z  optins = UserOptins() 2025-12-04T08:44:54.0415176Z  for user in user_optin_text.split("\n"): 2025-12-04T08:44:54.0415815Z  user = user.strip("\r\n\t -") 2025-12-04T08:44:54.0416445Z  if not user or not user.startswith("@"): 2025-12-04T08:44:54.0417068Z  # Not a valid user. Skip 2025-12-04T08:44:54.0417628Z  continue 2025-12-04T08:44:54.0418088Z  2025-12-04T08:44:54.0418470Z  if user: 2025-12-04T08:44:54.0418993Z  usr_name = user.split(",")[0].strip("@") 2025-12-04T08:44:54.0419874Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:44:54.0420605Z  2025-12-04T08:44:54.0421006Z  return optins 2025-12-04T08:44:54.0421471Z  2025-12-04T08:44:54.0421841Z  2025-12-04T08:44:54.0422391Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:44:54.0423057Z  """ 2025-12-04T08:44:54.0423528Z  Check if the experiment name is valid. 2025-12-04T08:44:54.0424135Z  A valid name: 2025-12-04T08:44:54.0424883Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:44:54.0425901Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:44:54.0426677Z  - Cannot contain spaces 2025-12-04T08:44:54.0427204Z  """ 2025-12-04T08:44:54.0427595Z  2025-12-04T08:44:54.0428107Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:44:54.0428905Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:44:54.0429778Z  2025-12-04T08:44:54.0430170Z  if valid: 2025-12-04T08:44:54.0430625Z  return True 2025-12-04T08:44:54.0431082Z  2025-12-04T08:44:54.0431469Z  log.error( 2025-12-04T08:44:54.0433036Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-12-04T08:44:54.0434675Z  ) 2025-12-04T08:44:54.0435087Z  return False 2025-12-04T08:44:54.0435545Z  2025-12-04T08:44:54.0435907Z  2025-12-04T08:44:54.0436486Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:44:54.0437183Z  """ 2025-12-04T08:44:54.0437852Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:44:54.0438630Z  """ 2025-12-04T08:44:54.0439036Z  try: 2025-12-04T08:44:54.0439573Z  if settings_text: 2025-12-04T08:44:54.0440413Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:44:54.0441276Z  # for easy reading 2025-12-04T08:44:54.0442175Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:44:54.0443148Z  # the backtick character in shell commands. 2025-12-04T08:44:54.0443825Z  backtick = chr(96) # backtick character 2025-12-04T08:44:54.0444579Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:44:54.0445325Z  settings = load_yaml(settings_text) 2025-12-04T08:44:54.0445885Z  2025-12-04T08:44:54.0446545Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:44:54.0447486Z  experiments = {} 2025-12-04T08:44:54.0448007Z  2025-12-04T08:44:54.0448625Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:44:54.0449560Z  if not is_valid_experiment_name(exp_name): 2025-12-04T08:44:54.0450756Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-12-04T08:44:54.0451861Z  continue 2025-12-04T08:44:54.0452365Z  2025-12-04T08:44:54.0452777Z  valid_settings = {} 2025-12-04T08:44:54.0453383Z  for setting in exp_settings: 2025-12-04T08:44:54.0454028Z  if setting not in Experiment._fields: 2025-12-04T08:44:54.0454653Z  log.warning( 2025-12-04T08:44:54.0455456Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:44:54.0456222Z  ) 2025-12-04T08:44:54.0456723Z  else: 2025-12-04T08:44:54.0457334Z  valid_settings[setting] = exp_settings[setting] 2025-12-04T08:44:54.0457951Z  2025-12-04T08:44:54.0458473Z  experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:44:54.0459183Z  return Settings(experiments) 2025-12-04T08:44:54.0459835Z  2025-12-04T08:44:54.0460228Z  except Exception: 2025-12-04T08:44:54.0460806Z  log.exception("Failed to parse settings") 2025-12-04T08:44:54.0461390Z  2025-12-04T08:44:54.0461787Z  return Settings() 2025-12-04T08:44:54.0462269Z  2025-12-04T08:44:54.0462638Z  2025-12-04T08:44:54.0463263Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:44:54.0463897Z  """ 2025-12-04T08:44:54.0464418Z  Parse settings, if any, from the rollout state. 2025-12-04T08:44:54.0465023Z  2025-12-04T08:44:54.0465618Z  If the issue body contains "---" then the text above that is the settings 2025-12-04T08:44:54.0466455Z  and the text below is the list of opted in users. 2025-12-04T08:44:54.0467066Z  2025-12-04T08:44:54.0467730Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:44:54.0468510Z  """ 2025-12-04T08:44:54.0469146Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:44:54.0470090Z  return parse_settings_from_text(settings_text) 2025-12-04T08:44:54.0470686Z  2025-12-04T08:44:54.0471049Z  2025-12-04T08:44:54.0471557Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:44:54.0472183Z  """ 2025-12-04T08:44:54.0472637Z  Parse users from the rollout state. 2025-12-04T08:44:54.0473197Z  2025-12-04T08:44:54.0473562Z  """ 2025-12-04T08:44:54.0474173Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:44:54.0474994Z  return parse_user_opt_in_from_text(users_text) 2025-12-04T08:44:54.0475599Z  2025-12-04T08:44:54.0475969Z  2025-12-04T08:44:54.0476650Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:44:54.0477466Z  """ 2025-12-04T08:44:54.0477951Z  Check if a user is opted into an experiment 2025-12-04T08:44:54.0478543Z  """ 2025-12-04T08:44:54.0479073Z  return experiment_name in user_optins.get(user, []) 2025-12-04T08:44:54.0479812Z  2025-12-04T08:44:54.0480384Z  2025-12-04T08:44:54.0481083Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:44:54.0481911Z  """ 2025-12-04T08:44:54.0482446Z  Check if a user explicitly opted out of an experiment 2025-12-04T08:44:54.0483081Z  """ 2025-12-04T08:44:54.0483667Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:44:54.0484442Z  experiment_optout = "-" + experiment_name 2025-12-04T08:44:54.0485170Z  if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:44:54.0485835Z  return False 2025-12-04T08:44:54.0486299Z  2025-12-04T08:44:54.0486805Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:44:54.0487465Z  log.warning( 2025-12-04T08:44:54.0488393Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:44:54.0489339Z  ) 2025-12-04T08:44:54.0489851Z  2025-12-04T08:44:54.0490241Z  return True 2025-12-04T08:44:54.0490690Z  2025-12-04T08:44:54.0491057Z  2025-12-04T08:44:54.0491458Z def get_runner_prefix( 2025-12-04T08:44:54.0491976Z  rollout_state: str, 2025-12-04T08:44:54.0492532Z  workflow_requestors: Iterable[str], 2025-12-04T08:44:54.0493102Z  branch: str, 2025-12-04T08:44:54.0493691Z  eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:44:54.0494449Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:44:54.0495098Z  is_canary: bool = False, 2025-12-04T08:44:54.0495616Z ) -> str: 2025-12-04T08:44:54.0496122Z  settings = parse_settings(rollout_state) 2025-12-04T08:44:54.0496787Z  user_optins = parse_users(rollout_state) 2025-12-04T08:44:54.0497361Z  2025-12-04T08:44:54.0497884Z  fleet_prefix = "" 2025-12-04T08:44:54.0498391Z  prefixes = [] 2025-12-04T08:44:54.0499121Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:44:54.0500257Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:44:54.0501026Z  log.info( 2025-12-04T08:44:54.0501800Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:44:54.0502607Z  ) 2025-12-04T08:44:54.0503075Z  continue 2025-12-04T08:44:54.0503539Z  2025-12-04T08:44:54.0503954Z  if opt_out_experiments: 2025-12-04T08:44:54.0504573Z  if experiment_name in opt_out_experiments: 2025-12-04T08:44:54.0505298Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:44:54.0505947Z  log.info( 2025-12-04T08:44:54.0506971Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:44:54.0508021Z  ) 2025-12-04T08:44:54.0508501Z  continue 2025-12-04T08:44:54.0509022Z  2025-12-04T08:44:54.0509537Z  if eligible_experiments: 2025-12-04T08:44:54.0510183Z  if experiment_name not in eligible_experiments: 2025-12-04T08:44:54.0510892Z  exp_list = ", ".join(eligible_experiments) 2025-12-04T08:44:54.0511503Z  log.info( 2025-12-04T08:44:54.0512378Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:44:54.0513269Z  ) 2025-12-04T08:44:54.0513739Z  continue 2025-12-04T08:44:54.0514443Z  elif not experiment_settings.default: 2025-12-04T08:44:54.0515025Z  log.info( 2025-12-04T08:44:54.0515784Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:44:54.0516573Z  ) 2025-12-04T08:44:54.0517021Z  continue 2025-12-04T08:44:54.0517487Z  2025-12-04T08:44:54.0518003Z  # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:44:54.0518678Z  opted_out_users = [ 2025-12-04T08:44:54.0519204Z  requestor 2025-12-04T08:44:54.0519853Z  for requestor in workflow_requestors 2025-12-04T08:44:54.0520605Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:44:54.0521288Z  ] 2025-12-04T08:44:54.0521695Z  2025-12-04T08:44:54.0522106Z  if opted_out_users: 2025-12-04T08:44:54.0522659Z  log.info( 2025-12-04T08:44:54.0523377Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:44:54.0524160Z  ) 2025-12-04T08:44:54.0524607Z  continue 2025-12-04T08:44:54.0525074Z  2025-12-04T08:44:54.0525587Z  # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:44:54.0526259Z  opted_in_users = [ 2025-12-04T08:44:54.0526791Z  requestor 2025-12-04T08:44:54.0527336Z  for requestor in workflow_requestors 2025-12-04T08:44:54.0528079Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:44:54.0528756Z  ] 2025-12-04T08:44:54.0529168Z  2025-12-04T08:44:54.0529652Z  enabled = False 2025-12-04T08:44:54.0530168Z  if opted_in_users: 2025-12-04T08:44:54.0530807Z  log.info( 2025-12-04T08:44:54.0531521Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:44:54.0532274Z  ) 2025-12-04T08:44:54.0532734Z  enabled = True 2025-12-04T08:44:54.0533236Z  2025-12-04T08:44:54.0533689Z  elif experiment_settings.rollout_perc: 2025-12-04T08:44:54.0534615Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:44:54.0535643Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:44:54.0536358Z  log.info( 2025-12-04T08:44:54.0537323Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:44:54.0538298Z  ) 2025-12-04T08:44:54.0538790Z  enabled = True 2025-12-04T08:44:54.0539304Z  2025-12-04T08:44:54.0539803Z  if enabled: 2025-12-04T08:44:54.0540321Z  label = experiment_name 2025-12-04T08:44:54.0540951Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:44:54.0541867Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:44:54.0542811Z  # - If it's enabled, then we always list it's prefix first 2025-12-04T08:44:54.0543673Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:44:54.0544397Z  if is_canary: 2025-12-04T08:44:54.0544975Z  label += CANARY_FLEET_SUFFIX 2025-12-04T08:44:54.0545595Z  fleet_prefix = label 2025-12-04T08:44:54.0546148Z  else: 2025-12-04T08:44:54.0546811Z  prefixes.append(label) 2025-12-04T08:44:54.0547366Z  2025-12-04T08:44:54.0547770Z  if len(prefixes) > 1: 2025-12-04T08:44:54.0548283Z  log.error( 2025-12-04T08:44:54.0549623Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-12-04T08:44:54.0550801Z  ) 2025-12-04T08:44:54.0551251Z  prefixes = prefixes[:1] 2025-12-04T08:44:54.0551775Z  2025-12-04T08:44:54.0552182Z  # Fleet always comes first 2025-12-04T08:44:54.0552731Z  if fleet_prefix: 2025-12-04T08:44:54.0553255Z  prefixes.insert(0, fleet_prefix) 2025-12-04T08:44:54.0554052Z  2025-12-04T08:44:54.0554608Z  return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:44:54.0555236Z  2025-12-04T08:44:54.0555614Z  2025-12-04T08:44:54.0556323Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:44:54.0557157Z  """ 2025-12-04T08:44:54.0557808Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:44:54.0558585Z  2025-12-04T08:44:54.0559215Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:44:54.0560083Z  """ 2025-12-04T08:44:54.0560538Z  gh = get_gh_client(github_token) 2025-12-04T08:44:54.0561163Z  issue = get_issue(gh, repo, issue_num) 2025-12-04T08:44:54.0561883Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:44:54.0562531Z  2025-12-04T08:44:54.0562899Z  2025-12-04T08:44:54.0563551Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:44:54.0564524Z  for _ in range(num_retries): 2025-12-04T08:44:54.0565078Z  try: 2025-12-04T08:44:54.0565571Z  req = Request(url=url, headers=headers) 2025-12-04T08:44:54.0566306Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:44:54.0567016Z  return json.loads(content) 2025-12-04T08:44:54.0567606Z  except Exception as e: 2025-12-04T08:44:54.0568236Z  log.warning(f"Could not download {url}: {e}") 2025-12-04T08:44:54.0568831Z  2025-12-04T08:44:54.0569564Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:44:54.0570342Z  return {} 2025-12-04T08:44:54.0570784Z  2025-12-04T08:44:54.0571152Z  2025-12-04T08:44:54.0571528Z @cache 2025-12-04T08:44:54.0572229Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:44:54.0573063Z  """ 2025-12-04T08:44:54.0573517Z  Dynamically get PR information 2025-12-04T08:44:54.0574056Z  """ 2025-12-04T08:44:54.0574632Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:44:54.0575314Z  headers = { 2025-12-04T08:44:54.0575861Z  "Accept": "application/vnd.github.v3+json", 2025-12-04T08:44:54.0576534Z  "Authorization": f"token {github_token}", 2025-12-04T08:44:54.0577120Z  } 2025-12-04T08:44:54.0577621Z  json_response: dict[str, Any] = download_json( 2025-12-04T08:44:54.0578298Z  url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:44:54.0578891Z  headers=headers, 2025-12-04T08:44:54.0579468Z  ) 2025-12-04T08:44:54.0579872Z  2025-12-04T08:44:54.0580270Z  if not json_response: 2025-12-04T08:44:54.0580944Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:44:54.0581772Z  return {} 2025-12-04T08:44:54.0582232Z  2025-12-04T08:44:54.0582636Z  return json_response 2025-12-04T08:44:54.0583124Z  2025-12-04T08:44:54.0583492Z  2025-12-04T08:44:54.0584132Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:44:54.0584917Z  """ 2025-12-04T08:44:54.0585511Z  Dynamically get the latest list of labels from the pull request 2025-12-04T08:44:54.0586220Z  """ 2025-12-04T08:44:54.0586776Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:44:54.0587437Z  return { 2025-12-04T08:44:54.0588101Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:44:54.0588840Z  } 2025-12-04T08:44:54.0589238Z  2025-12-04T08:44:54.0589715Z  2025-12-04T08:44:54.0590112Z def main() -> None: 2025-12-04T08:44:54.0590604Z  args = parse_args() 2025-12-04T08:44:54.0591099Z  2025-12-04T08:44:54.0591563Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:44:54.0592149Z  2025-12-04T08:44:54.0592560Z  # Check if the PR is opt-out 2025-12-04T08:44:54.0593112Z  if args.pr_number: 2025-12-04T08:44:54.0593863Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:44:54.0594678Z  if OPT_OUT_LABEL in labels: 2025-12-04T08:44:54.0595238Z  log.info( 2025-12-04T08:44:54.0596031Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:44:54.0596845Z  ) 2025-12-04T08:44:54.0597496Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:44:54.0598237Z  sys.exit() 2025-12-04T08:44:54.0598841Z  2025-12-04T08:44:54.0599228Z  try: 2025-12-04T08:44:54.0599853Z  rollout_state = get_rollout_state_from_issue( 2025-12-04T08:44:54.0600639Z  args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:44:54.0601335Z  ) 2025-12-04T08:44:54.0601750Z  2025-12-04T08:44:54.0602182Z  username = get_potential_pr_author( 2025-12-04T08:44:54.0602781Z  args.github_token, 2025-12-04T08:44:54.0603330Z  args.github_repo, 2025-12-04T08:44:54.0603888Z  args.github_actor, 2025-12-04T08:44:54.0604441Z  args.github_ref_type, 2025-12-04T08:44:54.0605017Z  args.github_branch, 2025-12-04T08:44:54.0605550Z  ) 2025-12-04T08:44:54.0605956Z  2025-12-04T08:44:54.0606489Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:44:54.0607152Z  2025-12-04T08:44:54.0607618Z  runner_label_prefix = get_runner_prefix( 2025-12-04T08:44:54.0608217Z  rollout_state, 2025-12-04T08:44:54.0608795Z  (args.github_issue_owner, username), 2025-12-04T08:44:54.0609485Z  args.github_branch, 2025-12-04T08:44:54.0610073Z  args.eligible_experiments, 2025-12-04T08:44:54.0610686Z  args.opt_out_experiments, 2025-12-04T08:44:54.0611244Z  is_canary, 2025-12-04T08:44:54.0611731Z  ) 2025-12-04T08:44:54.0612137Z  2025-12-04T08:44:54.0612542Z  except Exception as e: 2025-12-04T08:44:54.0613062Z  log.error( 2025-12-04T08:44:54.0613840Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:44:54.0614652Z  ) 2025-12-04T08:44:54.0615200Z  2025-12-04T08:44:54.0615789Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:44:54.0616490Z  2025-12-04T08:44:54.0616862Z  2025-12-04T08:44:54.0617253Z if __name__ == "__main__": 2025-12-04T08:44:54.0617757Z  main() 2025-12-04T08:44:54.0618170Z  2025-12-04T08:44:54.0618544Z EOF 2025-12-04T08:44:54.0618917Z  2025-12-04T08:44:54.0619326Z cat runner_determinator.py 2025-12-04T08:44:54.1224176Z shell: /usr/bin/bash -e {0} 2025-12-04T08:44:54.1225083Z env: 2025-12-04T08:44:54.1225869Z GITHUB_TOKEN: *** 2025-12-04T08:44:54.1226353Z ISSUE_NUMBER: 5132 2025-12-04T08:44:54.1226840Z TRIGGERING_ACTOR: huydhn 2025-12-04T08:44:54.1227353Z ISSUE_OWNER: 2025-12-04T08:44:54.1227803Z CHECK_EXPERIMENTS: 2025-12-04T08:44:54.1228235Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:44:54.1228671Z PR_NUMBER: 2025-12-04T08:44:54.1229089Z ##[endgroup] 2025-12-04T08:44:54.1438855Z # flake8: noqa: G004 2025-12-04T08:44:54.1439199Z 2025-12-04T08:44:54.1439889Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-12-04T08:44:54.1440878Z # must be kept in sync. You can do it easily by running the following command: 2025-12-04T08:44:54.1441691Z # python .github/scripts/update_runner_determinator.py 2025-12-04T08:44:54.1442150Z 2025-12-04T08:44:54.1442309Z """ 2025-12-04T08:44:54.1442897Z This runner determinator is used to determine which set of runners to run a 2025-12-04T08:44:54.1443786Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-12-04T08:44:54.1444710Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-12-04T08:44:54.1445531Z of which runners should be used to run which job. 2025-12-04T08:44:54.1445942Z 2025-12-04T08:44:54.1446327Z The configuration has two parts, the settings and a list of opted-in users, 2025-12-04T08:44:54.1447440Z separated by a line containing "---". If the line is not present, the 2025-12-04T08:44:54.1448354Z settings are considered to be empty with only the second part, the user 2025-12-04T08:44:54.1449065Z list, defined. 2025-12-04T08:44:54.1449300Z 2025-12-04T08:44:54.1450027Z The first part is a YAML block that defines the rollout settings. This can be 2025-12-04T08:44:54.1451001Z used to define any settings that are needed to determine which runners to use. 2025-12-04T08:44:54.1451838Z It's fields are defined by the RolloutSettings class below. 2025-12-04T08:44:54.1452296Z 2025-12-04T08:44:54.1452664Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-12-04T08:44:54.1453543Z The user list is also a comma separated list of additional features or 2025-12-04T08:44:54.1454280Z experiments which the user could be opted in to. 2025-12-04T08:44:54.1454683Z 2025-12-04T08:44:54.1454886Z The user list has the following rules: 2025-12-04T08:44:54.1455239Z 2025-12-04T08:44:54.1455571Z - Users are GitHub usernames, which must start with the @ prefix 2025-12-04T08:44:54.1456449Z - Each user is also a comma-separated list of features/experiments to enable 2025-12-04T08:44:54.1457222Z - A "#" prefix opts the user out of all experiments 2025-12-04T08:44:54.1457625Z 2025-12-04T08:44:54.1457793Z Example config: 2025-12-04T08:44:54.1458252Z # A list of experiments that can be opted into. 2025-12-04T08:44:54.1458919Z # This defines the behavior they'll induce when opted into. 2025-12-04T08:44:54.1459724Z # Expected syntax is: 2025-12-04T08:44:54.1460382Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-12-04T08:44:54.1461373Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-12-04T08:44:54.1461988Z 2025-12-04T08:44:54.1462161Z experiments: 2025-12-04T08:44:54.1462550Z lf: 2025-12-04T08:44:54.1462939Z rollout_percent: 25 2025-12-04T08:44:54.1463392Z all_branches: false 2025-12-04T08:44:54.1464027Z default: true 2025-12-04T08:44:54.1464438Z --- 2025-12-04T08:44:54.1464644Z 2025-12-04T08:44:54.1464808Z # Opt-ins: 2025-12-04T08:44:54.1465395Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-12-04T08:44:54.1466324Z # and specifying experiments to enable in a comma-separated list. 2025-12-04T08:44:54.1467095Z # To always opt out of an experiment, prefix it with a "-". 2025-12-04T08:44:54.1467762Z # Experiments should be from the above list. 2025-12-04T08:44:54.1468149Z 2025-12-04T08:44:54.1468340Z @User1,-lf,split_build 2025-12-04T08:44:54.1468778Z @User2,lf 2025-12-04T08:44:54.1469167Z @User3,split_build 2025-12-04T08:44:54.1469752Z """ 2025-12-04T08:44:54.1469961Z 2025-12-04T08:44:54.1470125Z import json 2025-12-04T08:44:54.1470500Z import logging 2025-12-04T08:44:54.1470897Z import os 2025-12-04T08:44:54.1471268Z import random 2025-12-04T08:44:54.1471659Z import re 2025-12-04T08:44:54.1472030Z import sys 2025-12-04T08:44:54.1472460Z from argparse import ArgumentParser 2025-12-04T08:44:54.1473005Z from collections.abc import Iterable 2025-12-04T08:44:54.1473540Z from functools import cache 2025-12-04T08:44:54.1474033Z from logging import LogRecord 2025-12-04T08:44:54.1474546Z from typing import Any, NamedTuple 2025-12-04T08:44:54.1475096Z from urllib.request import Request, urlopen 2025-12-04T08:44:54.1475476Z 2025-12-04T08:44:54.1475642Z import yaml 2025-12-04T08:44:54.1476050Z from github import Auth, Github 2025-12-04T08:44:54.1476557Z from github.Issue import Issue 2025-12-04T08:44:54.1476876Z 2025-12-04T08:44:54.1476883Z 2025-12-04T08:44:54.1477106Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-12-04T08:44:54.1477812Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-12-04T08:44:54.1478704Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-12-04T08:44:54.1479282Z 2025-12-04T08:44:54.1480140Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-12-04T08:44:54.1480920Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-12-04T08:44:54.1481469Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-12-04T08:44:54.1482027Z OPT_OUT_LABEL = "no-runner-experiments" 2025-12-04T08:44:54.1482390Z 2025-12-04T08:44:54.1482591Z SETTING_EXPERIMENTS = "experiments" 2025-12-04T08:44:54.1482929Z 2025-12-04T08:44:54.1483139Z LF_FLEET_EXPERIMENT = "lf" 2025-12-04T08:44:54.1483600Z CANARY_FLEET_SUFFIX = ".c" 2025-12-04T08:44:54.1483890Z 2025-12-04T08:44:54.1483902Z 2025-12-04T08:44:54.1484092Z class Experiment(NamedTuple): 2025-12-04T08:44:54.1484585Z rollout_perc: float = ( 2025-12-04T08:44:54.1485229Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-12-04T08:44:54.1485909Z ) 2025-12-04T08:44:54.1486291Z all_branches: bool = ( 2025-12-04T08:44:54.1486934Z False # If True, the experiment is also enabled on the exception branches 2025-12-04T08:44:54.1487614Z ) 2025-12-04T08:44:54.1487981Z default: bool = ( 2025-12-04T08:44:54.1488555Z True # If True, the experiment is enabled by default for all queries 2025-12-04T08:44:54.1489590Z ) 2025-12-04T08:44:54.1489826Z 2025-12-04T08:44:54.1490012Z # Add more fields as needed 2025-12-04T08:44:54.1490331Z 2025-12-04T08:44:54.1490338Z 2025-12-04T08:44:54.1490526Z class Settings(NamedTuple): 2025-12-04T08:44:54.1490969Z """ 2025-12-04T08:44:54.1491421Z Settings for the experiments that can be opted into. 2025-12-04T08:44:54.1492004Z """ 2025-12-04T08:44:54.1492203Z 2025-12-04T08:44:54.1492419Z experiments: dict[str, Experiment] = {} 2025-12-04T08:44:54.1492790Z 2025-12-04T08:44:54.1492806Z 2025-12-04T08:44:54.1493015Z class ColorFormatter(logging.Formatter): 2025-12-04T08:44:54.1493636Z """Color codes the log messages based on the log level""" 2025-12-04T08:44:54.1494083Z 2025-12-04T08:44:54.1494247Z COLORS = { 2025-12-04T08:44:54.1494657Z "WARNING": "\033[33m", # Yellow 2025-12-04T08:44:54.1495337Z "ERROR": "\033[31m", # Red 2025-12-04T08:44:54.1495848Z "CRITICAL": "\033[31m", # Red 2025-12-04T08:44:54.1496365Z "INFO": "\033[0m", # Reset 2025-12-04T08:44:54.1496858Z "DEBUG": "\033[0m", # Reset 2025-12-04T08:44:54.1497334Z } 2025-12-04T08:44:54.1497536Z 2025-12-04T08:44:54.1497754Z def format(self, record: LogRecord) -> str: 2025-12-04T08:44:54.1498518Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-12-04T08:44:54.1499302Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-12-04T08:44:54.1500138Z return super().format(record) 2025-12-04T08:44:54.1500483Z 2025-12-04T08:44:54.1500490Z 2025-12-04T08:44:54.1500684Z handler = logging.StreamHandler() 2025-12-04T08:44:54.1501400Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-12-04T08:44:54.1501961Z 2025-12-04T08:44:54.1502205Z log = logging.getLogger(os.path.basename(__file__)) 2025-12-04T08:44:54.1502797Z log.addHandler(handler) 2025-12-04T08:44:54.1503260Z log.setLevel(logging.INFO) 2025-12-04T08:44:54.1503558Z 2025-12-04T08:44:54.1503564Z 2025-12-04T08:44:54.1503811Z def set_github_output(key: str, value: str) -> None: 2025-12-04T08:44:54.1504384Z """ 2025-12-04T08:44:54.1504882Z Defines outputs of the github action that invokes this script 2025-12-04T08:44:54.1505527Z """ 2025-12-04T08:44:54.1505900Z if not GITHUB_OUTPUT: 2025-12-04T08:44:54.1506983Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-12-04T08:44:54.1508138Z log.warning( 2025-12-04T08:44:54.1509038Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-12-04T08:44:54.1510175Z ) 2025-12-04T08:44:54.1519813Z print(f"::set-output name={key}::{value}") 2025-12-04T08:44:54.1520460Z return 2025-12-04T08:44:54.1520697Z 2025-12-04T08:44:54.1520919Z with open(GITHUB_OUTPUT, "a") as f: 2025-12-04T08:44:54.1521690Z log.info(f"Setting output: {key}='{value}'") 2025-12-04T08:44:54.1522291Z f.write(f"{key}={value}\n") 2025-12-04T08:44:54.1522627Z 2025-12-04T08:44:54.1522635Z 2025-12-04T08:44:54.1522937Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-12-04T08:44:54.1523579Z return frozenset( 2025-12-04T08:44:54.1524199Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-12-04T08:44:54.1524908Z ) 2025-12-04T08:44:54.1525107Z 2025-12-04T08:44:54.1525114Z 2025-12-04T08:44:54.1525301Z def parse_args() -> Any: 2025-12-04T08:44:54.1525881Z parser = ArgumentParser("Get dynamic rollout settings") 2025-12-04T08:44:54.1526768Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-12-04T08:44:54.1527556Z parser.add_argument( 2025-12-04T08:44:54.1528032Z "--github-issue-repo", 2025-12-04T08:44:54.1528503Z type=str, 2025-12-04T08:44:54.1528933Z required=False, 2025-12-04T08:44:54.1529603Z default="pytorch/test-infra", 2025-12-04T08:44:54.1530190Z help="GitHub repo to get the issue", 2025-12-04T08:44:54.1530709Z ) 2025-12-04T08:44:54.1531089Z parser.add_argument( 2025-12-04T08:44:54.1531538Z "--github-repo", 2025-12-04T08:44:54.1531982Z type=str, 2025-12-04T08:44:54.1532380Z required=True, 2025-12-04T08:44:54.1532854Z help="GitHub repo where CI is running", 2025-12-04T08:44:54.1533384Z ) 2025-12-04T08:44:54.1533762Z parser.add_argument( 2025-12-04T08:44:54.1534382Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-12-04T08:44:54.1535055Z ) 2025-12-04T08:44:54.1535431Z parser.add_argument( 2025-12-04T08:44:54.1536056Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-12-04T08:44:54.1536743Z ) 2025-12-04T08:44:54.1537106Z parser.add_argument( 2025-12-04T08:44:54.1537921Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-12-04T08:44:54.1538628Z ) 2025-12-04T08:44:54.1539007Z parser.add_argument( 2025-12-04T08:44:54.1539906Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-12-04T08:44:54.1540661Z ) 2025-12-04T08:44:54.1541044Z parser.add_argument( 2025-12-04T08:44:54.1541503Z "--github-ref-type", 2025-12-04T08:44:54.1541967Z type=str, 2025-12-04T08:44:54.1542371Z required=True, 2025-12-04T08:44:54.1542865Z help="Current GitHub ref type, branch or tag", 2025-12-04T08:44:54.1543423Z ) 2025-12-04T08:44:54.1543800Z parser.add_argument( 2025-12-04T08:44:54.1544264Z "--eligible-experiments", 2025-12-04T08:44:54.1544790Z type=_str_comma_separated_to_set, 2025-12-04T08:44:54.1545322Z required=False, 2025-12-04T08:44:54.1545752Z default="", 2025-12-04T08:44:54.1546623Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-12-04T08:44:54.1547585Z ) 2025-12-04T08:44:54.1597156Z parser.add_argument( 2025-12-04T08:44:54.1597864Z "--opt-out-experiments", 2025-12-04T08:44:54.1598473Z type=_str_comma_separated_to_set, 2025-12-04T08:44:54.1599009Z required=False, 2025-12-04T08:44:54.1599565Z default="", 2025-12-04T08:44:54.1599971Z help=( 2025-12-04T08:44:54.1600668Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-12-04T08:44:54.1601833Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-12-04T08:44:54.1602672Z ), 2025-12-04T08:44:54.1603027Z ) 2025-12-04T08:44:54.1603397Z parser.add_argument( 2025-12-04T08:44:54.1603850Z "--pr-number", 2025-12-04T08:44:54.1604262Z type=str, 2025-12-04T08:44:54.1604669Z required=False, 2025-12-04T08:44:54.1605104Z default="", 2025-12-04T08:44:54.1605770Z help="the optional PR number where this is run", 2025-12-04T08:44:54.1606355Z ) 2025-12-04T08:44:54.1606558Z 2025-12-04T08:44:54.1606754Z return parser.parse_args() 2025-12-04T08:44:54.1607065Z 2025-12-04T08:44:54.1607072Z 2025-12-04T08:44:54.1607488Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-12-04T08:44:54.1608251Z auth = Auth.Token(github_token) 2025-12-04T08:44:54.1608762Z return Github(auth=auth) 2025-12-04T08:44:54.1609096Z 2025-12-04T08:44:54.1609103Z 2025-12-04T08:44:54.1609690Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-12-04T08:44:54.1610506Z repo = gh.get_repo(repo) 2025-12-04T08:44:54.1611020Z return repo.get_issue(number=issue_num) 2025-12-04T08:44:54.1611384Z 2025-12-04T08:44:54.1611390Z 2025-12-04T08:44:54.1611582Z def get_potential_pr_author( 2025-12-04T08:44:54.1612233Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-12-04T08:44:54.1612918Z ) -> str: 2025-12-04T08:44:54.1613444Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-12-04T08:44:54.1614257Z # Fetch the actual username from the original PR. The PR number is 2025-12-04T08:44:54.1615003Z # embedded in the tag name: ciflow// 2025-12-04T08:44:54.1615428Z 2025-12-04T08:44:54.1615628Z gh = get_gh_client(github_token) 2025-12-04T08:44:54.1615969Z 2025-12-04T08:44:54.1616234Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-12-04T08:44:54.1616860Z split_tag = ref_name.split("/") 2025-12-04T08:44:54.1617360Z if ( 2025-12-04T08:44:54.1617765Z len(split_tag) == 3 2025-12-04T08:44:54.1618238Z and split_tag[0] == "ciflow" 2025-12-04T08:44:54.1618776Z and split_tag[2].isnumeric() 2025-12-04T08:44:54.1619285Z ): 2025-12-04T08:44:54.1619785Z pr_number = split_tag[2] 2025-12-04T08:44:54.1620443Z try: 2025-12-04T08:44:54.1620880Z repository = gh.get_repo(repo) 2025-12-04T08:44:54.1621499Z pull = repository.get_pull(number=int(pr_number)) 2025-12-04T08:44:54.1622104Z except Exception as e: 2025-12-04T08:44:54.1622630Z raise Exception( # noqa: TRY002 2025-12-04T08:44:54.1623297Z f"issue with pull request {pr_number} from repo {repository}" 2025-12-04T08:44:54.1623950Z ) from e 2025-12-04T08:44:54.1624491Z return pull.user.login # type: ignore[no-any-return] 2025-12-04T08:44:54.1625189Z # In all other cases, return the original input username 2025-12-04T08:44:54.1625792Z return username 2025-12-04T08:44:54.1626035Z 2025-12-04T08:44:54.1626042Z 2025-12-04T08:44:54.1626267Z def is_exception_branch(branch: str) -> bool: 2025-12-04T08:44:54.1626805Z """ 2025-12-04T08:44:54.1627441Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-12-04T08:44:54.1628235Z """ 2025-12-04T08:44:54.1628786Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-12-04T08:44:54.1629316Z 2025-12-04T08:44:54.1629324Z 2025-12-04T08:44:54.1629637Z def load_yaml(yaml_text: str) -> Any: 2025-12-04T08:44:54.1630147Z try: 2025-12-04T08:44:54.1630531Z data = yaml.safe_load(yaml_text) 2025-12-04T08:44:54.1631046Z return data 2025-12-04T08:44:54.1631462Z except yaml.YAMLError: 2025-12-04T08:44:54.1631949Z log.exception("Error loading YAML") 2025-12-04T08:44:54.1632462Z raise 2025-12-04T08:44:54.1632687Z 2025-12-04T08:44:54.1632694Z 2025-12-04T08:44:54.1633119Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-12-04T08:44:54.1633871Z """ 2025-12-04T08:44:54.1634490Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-12-04T08:44:54.1635095Z 2025-12-04T08:44:54.1635583Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:44:54.1636370Z and the text below is the list of opted in users. 2025-12-04T08:44:54.1636783Z 2025-12-04T08:44:54.1637162Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-12-04T08:44:54.1637880Z """ 2025-12-04T08:44:54.1638323Z rollout_state_parts = rollout_state.split("---") 2025-12-04T08:44:54.1638949Z if len(rollout_state_parts) >= 2: 2025-12-04T08:44:54.1639669Z return rollout_state_parts[0], rollout_state_parts[1] 2025-12-04T08:44:54.1640275Z else: 2025-12-04T08:44:54.1640658Z return "", rollout_state 2025-12-04T08:44:54.1640977Z 2025-12-04T08:44:54.1640985Z 2025-12-04T08:44:54.1641191Z class UserOptins(dict[str, list[str]]): 2025-12-04T08:44:54.1641701Z """ 2025-12-04T08:44:54.1642228Z Dictionary of users with a list of features they have opted into 2025-12-04T08:44:54.1642890Z """ 2025-12-04T08:44:54.1643093Z 2025-12-04T08:44:54.1643105Z 2025-12-04T08:44:54.1643455Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-12-04T08:44:54.1644130Z """ 2025-12-04T08:44:54.1644854Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-12-04T08:44:54.1645555Z 2025-12-04T08:44:54.1646184Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-12-04T08:44:54.1647193Z - Example line: "@User1,lf,split_build" 2025-12-04T08:44:54.1647891Z - A "#" prefix indicates the user is opted out of all experiments 2025-12-04T08:44:54.1648380Z 2025-12-04T08:44:54.1648386Z 2025-12-04T08:44:54.1648551Z """ 2025-12-04T08:44:54.1648926Z optins = UserOptins() 2025-12-04T08:44:54.1649543Z for user in user_optin_text.split("\n"): 2025-12-04T08:44:54.1650105Z user = user.strip("\r\n\t -") 2025-12-04T08:44:54.1650660Z if not user or not user.startswith("@"): 2025-12-04T08:44:54.1651367Z # Not a valid user. Skip 2025-12-04T08:44:54.1651872Z continue 2025-12-04T08:44:54.1652124Z 2025-12-04T08:44:54.1652293Z if user: 2025-12-04T08:44:54.1652736Z usr_name = user.split(",")[0].strip("@") 2025-12-04T08:44:54.1653443Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-12-04T08:44:54.1653939Z 2025-12-04T08:44:54.1654107Z return optins 2025-12-04T08:44:54.1654354Z 2025-12-04T08:44:54.1654361Z 2025-12-04T08:44:54.1654650Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-12-04T08:44:54.1655258Z """ 2025-12-04T08:44:54.1655656Z Check if the experiment name is valid. 2025-12-04T08:44:54.1656190Z A valid name: 2025-12-04T08:44:54.1656842Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-12-04T08:44:54.1657821Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-12-04T08:44:54.1658567Z - Cannot contain spaces 2025-12-04T08:44:54.1659046Z """ 2025-12-04T08:44:54.1659248Z 2025-12-04T08:44:54.1659630Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-12-04T08:44:54.1660356Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-12-04T08:44:54.1660811Z 2025-12-04T08:44:54.1660978Z if valid: 2025-12-04T08:44:54.1661359Z return True 2025-12-04T08:44:54.1661599Z 2025-12-04T08:44:54.1661769Z log.error( 2025-12-04T08:44:54.1663245Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-12-04T08:44:54.1664877Z ) 2025-12-04T08:44:54.1665241Z return False 2025-12-04T08:44:54.1665476Z 2025-12-04T08:44:54.1665483Z 2025-12-04T08:44:54.1665786Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-12-04T08:44:54.1666420Z """ 2025-12-04T08:44:54.1667148Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-12-04T08:44:54.1667912Z """ 2025-12-04T08:44:54.1668273Z try: 2025-12-04T08:44:54.1668651Z if settings_text: 2025-12-04T08:44:54.1669491Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-12-04T08:44:54.1670298Z # for easy reading 2025-12-04T08:44:54.1671095Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-12-04T08:44:54.1671983Z # the backtick character in shell commands. 2025-12-04T08:44:54.1672594Z backtick = chr(96) # backtick character 2025-12-04T08:44:54.1673262Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-12-04T08:44:54.1673936Z settings = load_yaml(settings_text) 2025-12-04T08:44:54.1674325Z 2025-12-04T08:44:54.1674745Z # For now we just load experiments. We can expand this if/when we add more settings 2025-12-04T08:44:54.1675531Z experiments = {} 2025-12-04T08:44:54.1675828Z 2025-12-04T08:44:54.1676215Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-12-04T08:44:54.1676996Z if not is_valid_experiment_name(exp_name): 2025-12-04T08:44:54.1678115Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-12-04T08:44:54.1679175Z continue 2025-12-04T08:44:54.1679613Z 2025-12-04T08:44:54.1679804Z valid_settings = {} 2025-12-04T08:44:54.1680333Z for setting in exp_settings: 2025-12-04T08:44:54.1680897Z if setting not in Experiment._fields: 2025-12-04T08:44:54.1681465Z log.warning( 2025-12-04T08:44:54.1682169Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-12-04T08:44:54.1683054Z ) 2025-12-04T08:44:54.1683489Z else: 2025-12-04T08:44:54.1684017Z valid_settings[setting] = exp_settings[setting] 2025-12-04T08:44:54.1684449Z 2025-12-04T08:44:54.1684736Z experiments[exp_name] = Experiment(**valid_settings) 2025-12-04T08:44:54.1685369Z return Settings(experiments) 2025-12-04T08:44:54.1685732Z 2025-12-04T08:44:54.1685907Z except Exception: 2025-12-04T08:44:54.1686383Z log.exception("Failed to parse settings") 2025-12-04T08:44:54.1686783Z 2025-12-04T08:44:54.1686954Z return Settings() 2025-12-04T08:44:54.1687211Z 2025-12-04T08:44:54.1687218Z 2025-12-04T08:44:54.1687474Z def parse_settings(rollout_state: str) -> Settings: 2025-12-04T08:44:54.1688048Z """ 2025-12-04T08:44:54.1688499Z Parse settings, if any, from the rollout state. 2025-12-04T08:44:54.1688912Z 2025-12-04T08:44:54.1689265Z If the issue body contains "---" then the text above that is the settings 2025-12-04T08:44:54.1690527Z and the text below is the list of opted in users. 2025-12-04T08:44:54.1690946Z 2025-12-04T08:44:54.1691365Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-12-04T08:44:54.1692116Z """ 2025-12-04T08:44:54.1692681Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:44:54.1693444Z return parse_settings_from_text(settings_text) 2025-12-04T08:44:54.1693846Z 2025-12-04T08:44:54.1693859Z 2025-12-04T08:44:54.1694107Z def parse_users(rollout_state: str) -> UserOptins: 2025-12-04T08:44:54.1694679Z """ 2025-12-04T08:44:54.1695077Z Parse users from the rollout state. 2025-12-04T08:44:54.1695433Z 2025-12-04T08:44:54.1695594Z """ 2025-12-04T08:44:54.1696130Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-12-04T08:44:54.1696895Z return parse_user_opt_in_from_text(users_text) 2025-12-04T08:44:54.1697307Z 2025-12-04T08:44:54.1697320Z 2025-12-04T08:44:54.1697896Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:44:54.1698684Z """ 2025-12-04T08:44:54.1699102Z Check if a user is opted into an experiment 2025-12-04T08:44:54.1699765Z """ 2025-12-04T08:44:54.1700219Z return experiment_name in user_optins.get(user, []) 2025-12-04T08:44:54.1700666Z 2025-12-04T08:44:54.1700673Z 2025-12-04T08:44:54.1701103Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-12-04T08:44:54.1701862Z """ 2025-12-04T08:44:54.1702317Z Check if a user explicitly opted out of an experiment 2025-12-04T08:44:54.1702907Z """ 2025-12-04T08:44:54.1703409Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-12-04T08:44:54.1704110Z experiment_optout = "-" + experiment_name 2025-12-04T08:44:54.1704751Z if experiment_optout not in user_optins.get(user, []): 2025-12-04T08:44:54.1705357Z return False 2025-12-04T08:44:54.1705625Z 2025-12-04T08:44:54.1705912Z if is_user_opted_in(user, user_optins, experiment_name): 2025-12-04T08:44:54.1706523Z log.warning( 2025-12-04T08:44:54.1707341Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-12-04T08:44:54.1708233Z ) 2025-12-04T08:44:54.1708453Z 2025-12-04T08:44:54.1708618Z return True 2025-12-04T08:44:54.1708850Z 2025-12-04T08:44:54.1708857Z 2025-12-04T08:44:54.1709071Z def get_runner_prefix( 2025-12-04T08:44:54.1709610Z rollout_state: str, 2025-12-04T08:44:54.1710079Z workflow_requestors: Iterable[str], 2025-12-04T08:44:54.1710600Z branch: str, 2025-12-04T08:44:54.1711095Z eligible_experiments: frozenset[str] = frozenset(), 2025-12-04T08:44:54.1711769Z opt_out_experiments: frozenset[str] = frozenset(), 2025-12-04T08:44:54.1712371Z is_canary: bool = False, 2025-12-04T08:44:54.1712832Z ) -> str: 2025-12-04T08:44:54.1713259Z settings = parse_settings(rollout_state) 2025-12-04T08:44:54.1714071Z user_optins = parse_users(rollout_state) 2025-12-04T08:44:54.1714454Z 2025-12-04T08:44:54.1714631Z fleet_prefix = "" 2025-12-04T08:44:54.1715062Z prefixes = [] 2025-12-04T08:44:54.1715704Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-12-04T08:44:54.1716665Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-12-04T08:44:54.1717386Z log.info( 2025-12-04T08:44:54.1718076Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-12-04T08:44:54.1718838Z ) 2025-12-04T08:44:54.1719225Z continue 2025-12-04T08:44:54.1719579Z 2025-12-04T08:44:54.1719772Z if opt_out_experiments: 2025-12-04T08:44:54.1720336Z if experiment_name in opt_out_experiments: 2025-12-04T08:44:54.1720994Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-12-04T08:44:54.1721598Z log.info( 2025-12-04T08:44:54.1722545Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-12-04T08:44:54.1723540Z ) 2025-12-04T08:44:54.1723958Z continue 2025-12-04T08:44:54.1724238Z 2025-12-04T08:44:54.1724431Z if eligible_experiments: 2025-12-04T08:44:54.1725291Z if experiment_name not in eligible_experiments: 2025-12-04T08:44:54.1725950Z exp_list = ", ".join(eligible_experiments) 2025-12-04T08:44:54.1726518Z log.info( 2025-12-04T08:44:54.1727324Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-12-04T08:44:54.1728179Z ) 2025-12-04T08:44:54.1728588Z continue 2025-12-04T08:44:54.1729069Z elif not experiment_settings.default: 2025-12-04T08:44:54.1729732Z log.info( 2025-12-04T08:44:54.1730601Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-12-04T08:44:54.1731385Z ) 2025-12-04T08:44:54.1731770Z continue 2025-12-04T08:44:54.1732021Z 2025-12-04T08:44:54.1732300Z # Is any workflow_requestor opted out to this experiment? 2025-12-04T08:44:54.1732934Z opted_out_users = [ 2025-12-04T08:44:54.1733384Z requestor 2025-12-04T08:44:54.1733844Z for requestor in workflow_requestors 2025-12-04T08:44:54.1734513Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-12-04T08:44:54.1735151Z ] 2025-12-04T08:44:54.1735358Z 2025-12-04T08:44:54.1735546Z if opted_out_users: 2025-12-04T08:44:54.1736002Z log.info( 2025-12-04T08:44:54.1736624Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-12-04T08:44:54.1737330Z ) 2025-12-04T08:44:54.1737719Z continue 2025-12-04T08:44:54.1737972Z 2025-12-04T08:44:54.1738258Z # Is any workflow_requestor opted in to this experiment? 2025-12-04T08:44:54.1738902Z opted_in_users = [ 2025-12-04T08:44:54.1739445Z requestor 2025-12-04T08:44:54.1739919Z for requestor in workflow_requestors 2025-12-04T08:44:54.1740595Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-12-04T08:44:54.1741228Z ] 2025-12-04T08:44:54.1741432Z 2025-12-04T08:44:54.1741609Z enabled = False 2025-12-04T08:44:54.1742043Z if opted_in_users: 2025-12-04T08:44:54.1742493Z log.info( 2025-12-04T08:44:54.1743100Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-12-04T08:44:54.1743793Z ) 2025-12-04T08:44:54.1744186Z enabled = True 2025-12-04T08:44:54.1744468Z 2025-12-04T08:44:54.1744688Z elif experiment_settings.rollout_perc: 2025-12-04T08:44:54.1745538Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-12-04T08:44:54.1746632Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-12-04T08:44:54.1747306Z log.info( 2025-12-04T08:44:54.1748179Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-12-04T08:44:54.1749118Z ) 2025-12-04T08:44:54.1749638Z enabled = True 2025-12-04T08:44:54.1749951Z 2025-12-04T08:44:54.1750121Z if enabled: 2025-12-04T08:44:54.1750558Z label = experiment_name 2025-12-04T08:44:54.1751115Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-12-04T08:44:54.1751964Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-12-04T08:44:54.1752862Z # - If it's enabled, then we always list it's prefix first 2025-12-04T08:44:54.1753637Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-12-04T08:44:54.1754331Z if is_canary: 2025-12-04T08:44:54.1754836Z label += CANARY_FLEET_SUFFIX 2025-12-04T08:44:54.1755396Z fleet_prefix = label 2025-12-04T08:44:54.1755889Z else: 2025-12-04T08:44:54.1756326Z prefixes.append(label) 2025-12-04T08:44:54.1756678Z 2025-12-04T08:44:54.1756864Z if len(prefixes) > 1: 2025-12-04T08:44:54.1757314Z log.error( 2025-12-04T08:44:54.1758370Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-12-04T08:44:54.1759616Z ) 2025-12-04T08:44:54.1760008Z prefixes = prefixes[:1] 2025-12-04T08:44:54.1760331Z 2025-12-04T08:44:54.1760523Z # Fleet always comes first 2025-12-04T08:44:54.1761004Z if fleet_prefix: 2025-12-04T08:44:54.1761451Z prefixes.insert(0, fleet_prefix) 2025-12-04T08:44:54.1761820Z 2025-12-04T08:44:54.1762202Z return ".".join(prefixes) + "." if prefixes else "" 2025-12-04T08:44:54.1762632Z 2025-12-04T08:44:54.1762640Z 2025-12-04T08:44:54.1763094Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-12-04T08:44:54.1763885Z """ 2025-12-04T08:44:54.1764478Z Gets the first comment of the issue, which contains the desired rollout state. 2025-12-04T08:44:54.1765055Z 2025-12-04T08:44:54.1765441Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-12-04T08:44:54.1766157Z """ 2025-12-04T08:44:54.1766545Z gh = get_gh_client(github_token) 2025-12-04T08:44:54.1767099Z issue = get_issue(gh, repo, issue_num) 2025-12-04T08:44:54.1767736Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-12-04T08:44:54.1768195Z 2025-12-04T08:44:54.1768203Z 2025-12-04T08:44:54.1768616Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-12-04T08:44:54.1769496Z for _ in range(num_retries): 2025-12-04T08:44:54.1769985Z try: 2025-12-04T08:44:54.1770415Z req = Request(url=url, headers=headers) 2025-12-04T08:44:54.1771084Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-12-04T08:44:54.1771742Z return json.loads(content) 2025-12-04T08:44:54.1772276Z except Exception as e: 2025-12-04T08:44:54.1772823Z log.warning(f"Could not download {url}: {e}") 2025-12-04T08:44:54.1773234Z 2025-12-04T08:44:54.1773623Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-12-04T08:44:54.1774342Z return {} 2025-12-04T08:44:54.1774571Z 2025-12-04T08:44:54.1774578Z 2025-12-04T08:44:54.1774744Z @cache 2025-12-04T08:44:54.1775368Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-12-04T08:44:54.1776137Z """ 2025-12-04T08:44:54.1776525Z Dynamically get PR information 2025-12-04T08:44:54.1777030Z """ 2025-12-04T08:44:54.1777666Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-12-04T08:44:54.1778314Z headers = { 2025-12-04T08:44:54.1778786Z "Accept": "application/vnd.github.v3+json", 2025-12-04T08:44:54.1779507Z "Authorization": f"token {github_token}", 2025-12-04T08:44:54.1780062Z } 2025-12-04T08:44:54.1780490Z json_response: dict[str, Any] = download_json( 2025-12-04T08:44:54.1781106Z url=f"{github_api}/issues/{pr_number}", 2025-12-04T08:44:54.1781667Z headers=headers, 2025-12-04T08:44:54.1782105Z ) 2025-12-04T08:44:54.1782307Z 2025-12-04T08:44:54.1782494Z if not json_response: 2025-12-04T08:44:54.1783073Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-12-04T08:44:54.1783692Z return {} 2025-12-04T08:44:54.1783939Z 2025-12-04T08:44:54.1784125Z return json_response 2025-12-04T08:44:54.1784405Z 2025-12-04T08:44:54.1784412Z 2025-12-04T08:44:54.1784823Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-12-04T08:44:54.1785570Z """ 2025-12-04T08:44:54.1786110Z Dynamically get the latest list of labels from the pull request 2025-12-04T08:44:54.1786778Z """ 2025-12-04T08:44:54.1787271Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-12-04T08:44:54.1787892Z return { 2025-12-04T08:44:54.1788516Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-12-04T08:44:54.1789233Z } 2025-12-04T08:44:54.1789532Z 2025-12-04T08:44:54.1789539Z 2025-12-04T08:44:54.1789721Z def main() -> None: 2025-12-04T08:44:54.1790161Z args = parse_args() 2025-12-04T08:44:54.1790436Z 2025-12-04T08:44:54.1790662Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-12-04T08:44:54.1791067Z 2025-12-04T08:44:54.1791263Z # Check if the PR is opt-out 2025-12-04T08:44:54.1791764Z if args.pr_number: 2025-12-04T08:44:54.1792431Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-12-04T08:44:54.1793207Z if OPT_OUT_LABEL in labels: 2025-12-04T08:44:54.1793827Z log.info( 2025-12-04T08:44:54.1794539Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-12-04T08:44:54.1795314Z ) 2025-12-04T08:44:54.1795875Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:44:54.1796559Z sys.exit() 2025-12-04T08:44:54.1796831Z 2025-12-04T08:44:54.1796996Z try: 2025-12-04T08:44:54.1797432Z rollout_state = get_rollout_state_from_issue( 2025-12-04T08:44:54.1798156Z args.github_token, args.github_issue_repo, args.github_issue 2025-12-04T08:44:54.1798804Z ) 2025-12-04T08:44:54.1799012Z 2025-12-04T08:44:54.1799219Z username = get_potential_pr_author( 2025-12-04T08:44:54.1799889Z args.github_token, 2025-12-04T08:44:54.1800370Z args.github_repo, 2025-12-04T08:44:54.1800853Z args.github_actor, 2025-12-04T08:44:54.1801344Z args.github_ref_type, 2025-12-04T08:44:54.1801853Z args.github_branch, 2025-12-04T08:44:54.1802333Z ) 2025-12-04T08:44:54.1802544Z 2025-12-04T08:44:54.1802830Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-12-04T08:44:54.1803284Z 2025-12-04T08:44:54.1803509Z runner_label_prefix = get_runner_prefix( 2025-12-04T08:44:54.1804072Z rollout_state, 2025-12-04T08:44:54.1804565Z (args.github_issue_owner, username), 2025-12-04T08:44:54.1805117Z args.github_branch, 2025-12-04T08:44:54.1805624Z args.eligible_experiments, 2025-12-04T08:44:54.1806159Z args.opt_out_experiments, 2025-12-04T08:44:54.1806673Z is_canary, 2025-12-04T08:44:54.1807092Z ) 2025-12-04T08:44:54.1807305Z 2025-12-04T08:44:54.1807490Z except Exception as e: 2025-12-04T08:44:54.1807959Z log.error( 2025-12-04T08:44:54.1808628Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-12-04T08:44:54.1809705Z ) 2025-12-04T08:44:54.1809919Z 2025-12-04T08:44:54.1810250Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-12-04T08:44:54.1810765Z 2025-12-04T08:44:54.1810771Z 2025-12-04T08:44:54.1810949Z if __name__ == "__main__": 2025-12-04T08:44:54.1811407Z main() 2025-12-04T08:44:54.1811617Z 2025-12-04T08:44:54.1907914Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:44:54.1908844Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-12-04T08:44:54.1942543Z shell: /usr/bin/bash -e {0} 2025-12-04T08:44:54.1943026Z env: 2025-12-04T08:44:54.1943676Z GITHUB_TOKEN: *** 2025-12-04T08:44:54.1944091Z ISSUE_NUMBER: 5132 2025-12-04T08:44:54.1944523Z TRIGGERING_ACTOR: huydhn 2025-12-04T08:44:54.1944985Z ISSUE_OWNER: 2025-12-04T08:44:54.1945394Z CHECK_EXPERIMENTS: 2025-12-04T08:44:54.1945826Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:44:54.1946256Z PR_NUMBER: 2025-12-04T08:44:54.1946648Z ##[endgroup] 2025-12-04T08:44:54.6326973Z Defaulting to user installation because normal site-packages is not writeable 2025-12-04T08:44:55.1089934Z Collecting urllib3==1.26.18 2025-12-04T08:44:55.1441075Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-12-04T08:44:55.1664708Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.7 MB/s eta 0:00:00 2025-12-04T08:44:55.1937274Z Collecting PyGithub==2.3.0 2025-12-04T08:44:55.1972607Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-12-04T08:44:55.2455254Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-12-04T08:44:55.2487250Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl.metadata (9.8 kB) 2025-12-04T08:44:55.2535554Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-12-04T08:44:55.2552039Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-12-04T08:44:55.2566368Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-12-04T08:44:55.2815004Z Collecting Deprecated (from PyGithub==2.3.0) 2025-12-04T08:44:55.2847887Z Downloading deprecated-1.3.1-py2.py3-none-any.whl.metadata (5.9 kB) 2025-12-04T08:44:55.3077252Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-12-04T08:44:55.4388480Z Collecting cffi>=2.0.0 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:44:55.4429218Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.6 kB) 2025-12-04T08:44:55.6095109Z Collecting wrapt<3,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-12-04T08:44:55.6130715Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (9.0 kB) 2025-12-04T08:44:55.6338085Z Collecting pycparser (from cffi>=2.0.0->pynacl>=1.4.0->PyGithub==2.3.0) 2025-12-04T08:44:55.6367773Z Downloading pycparser-2.23-py3-none-any.whl.metadata (993 bytes) 2025-12-04T08:44:55.6596494Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-12-04T08:44:55.6657879Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 34.2 MB/s eta 0:00:00 2025-12-04T08:44:55.6703782Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-12-04T08:44:55.6765129Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 77.8 MB/s eta 0:00:00 2025-12-04T08:44:55.6804739Z Downloading pynacl-1.6.1-cp38-abi3-manylinux_2_34_x86_64.whl (1.4 MB) 2025-12-04T08:44:55.6929924Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 134.2 MB/s eta 0:00:00 2025-12-04T08:44:55.6966115Z Downloading deprecated-1.3.1-py2.py3-none-any.whl (11 kB) 2025-12-04T08:44:55.7025711Z Downloading cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (219 kB) 2025-12-04T08:44:55.7075255Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 219.6/219.6 kB 65.0 MB/s eta 0:00:00 2025-12-04T08:44:55.7110981Z Downloading wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (121 kB) 2025-12-04T08:44:55.7154787Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 121.5/121.5 kB 39.8 MB/s eta 0:00:00 2025-12-04T08:44:55.7185960Z Downloading pycparser-2.23-py3-none-any.whl (118 kB) 2025-12-04T08:44:55.7229995Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.1/118.1 kB 38.3 MB/s eta 0:00:00 2025-12-04T08:44:56.0173216Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-12-04T08:44:56.5628647Z Successfully installed Deprecated-1.3.1 PyGithub-2.3.0 cffi-2.0.0 pycparser-2.23 pynacl-1.6.1 urllib3-1.26.18 wrapt-2.0.1 2025-12-04T08:44:56.6535136Z ##[group]Run curr_branch="main" 2025-12-04T08:44:56.6535448Z curr_branch="main" 2025-12-04T08:44:56.6535674Z curr_ref_type="branch" 2025-12-04T08:44:56.6535943Z echo "Current branch is '$curr_branch'" 2025-12-04T08:44:56.6536224Z  2025-12-04T08:44:56.6536418Z python3 runner_determinator.py \ 2025-12-04T08:44:56.6536698Z  --github-token "$GITHUB_TOKEN" \ 2025-12-04T08:44:56.6536977Z  --github-issue "$ISSUE_NUMBER" \ 2025-12-04T08:44:56.6537237Z  --github-branch "$curr_branch" \ 2025-12-04T08:44:56.6537495Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-12-04T08:44:56.6537799Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-12-04T08:44:56.6538095Z  --github-ref-type "$curr_ref_type" \ 2025-12-04T08:44:56.6538374Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-12-04T08:44:56.6538669Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-12-04T08:44:56.6539039Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-12-04T08:44:56.6539333Z  --pr-number "${PR_NUMBER}" 2025-12-04T08:44:56.6574075Z shell: /usr/bin/bash -e {0} 2025-12-04T08:44:56.6574316Z env: 2025-12-04T08:44:56.6574882Z GITHUB_TOKEN: *** 2025-12-04T08:44:56.6575097Z ISSUE_NUMBER: 5132 2025-12-04T08:44:56.6575300Z TRIGGERING_ACTOR: huydhn 2025-12-04T08:44:56.6575501Z ISSUE_OWNER: 2025-12-04T08:44:56.6575687Z CHECK_EXPERIMENTS: 2025-12-04T08:44:56.6575876Z OPT_OUT_EXPERIMENTS: 2025-12-04T08:44:56.6576062Z PR_NUMBER: 2025-12-04T08:44:56.6576230Z ##[endgroup] 2025-12-04T08:44:56.6627145Z Current branch is 'main' 2025-12-04T08:44:58.4048468Z INFO : Based on rollout percentage of 75%, enabling experiment lf. 2025-12-04T08:44:58.4049886Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-12-04T08:44:58.4050875Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-12-04T08:44:58.4051793Z INFO : Branch main is an exception branch. Not enabling experiment wincanarylf. 2025-12-04T08:44:58.4052479Z INFO : Setting output: label-type='lf.' 2025-12-04T08:44:58.4370183Z Evaluate and set job outputs 2025-12-04T08:44:58.4376412Z Set output 'label-type' 2025-12-04T08:44:58.4378349Z Cleaning up orphan processes