2025-08-14T20:54:40.6784647Z Current runner version: '2.327.1' 2025-08-14T20:54:40.6810986Z ##[group]Runner Image Provisioner 2025-08-14T20:54:40.6812051Z Hosted Compute Agent 2025-08-14T20:54:40.6812621Z Version: 20250812.370 2025-08-14T20:54:40.6813161Z Commit: 4a2b2bf7520004e3e907c2150c8cabe342a3da32 2025-08-14T20:54:40.6813963Z Build Date: 2025-08-12T16:08:14Z 2025-08-14T20:54:40.6814552Z ##[endgroup] 2025-08-14T20:54:40.6815022Z ##[group]Operating System 2025-08-14T20:54:40.6815684Z Ubuntu 2025-08-14T20:54:40.6816149Z 24.04.2 2025-08-14T20:54:40.6816590Z LTS 2025-08-14T20:54:40.6817074Z ##[endgroup] 2025-08-14T20:54:40.6817563Z ##[group]Runner Image 2025-08-14T20:54:40.6818071Z Image: ubuntu-24.04 2025-08-14T20:54:40.6818649Z Version: 20250810.1.0 2025-08-14T20:54:40.6819684Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250810.1/images/ubuntu/Ubuntu2404-Readme.md 2025-08-14T20:54:40.6821561Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250810.1 2025-08-14T20:54:40.6822604Z ##[endgroup] 2025-08-14T20:54:40.6823647Z ##[group]GITHUB_TOKEN Permissions 2025-08-14T20:54:40.6825862Z Contents: read 2025-08-14T20:54:40.6826407Z Metadata: read 2025-08-14T20:54:40.6826917Z ##[endgroup] 2025-08-14T20:54:40.6829298Z Secret source: Actions 2025-08-14T20:54:40.6830200Z Prepare workflow directory 2025-08-14T20:54:40.7342273Z Prepare all required actions 2025-08-14T20:54:40.7398897Z Uses: pytorch/pytorch/.github/workflows/_runner-determinator.yml@refs/heads/main (1fc683cf17c8c673044538d10266c00f92987be2) 2025-08-14T20:54:40.7404045Z ##[group] Inputs 2025-08-14T20:54:40.7404726Z check_experiments: 2025-08-14T20:54:40.7405369Z opt_out_experiments: 2025-08-14T20:54:40.7405891Z triggering_actor: huydhn 2025-08-14T20:54:40.7406513Z issue_owner: 2025-08-14T20:54:40.7407014Z curr_branch: main 2025-08-14T20:54:40.7407498Z curr_ref_type: branch 2025-08-14T20:54:40.7408234Z issue_number: 5132 2025-08-14T20:54:40.7408768Z ##[endgroup] 2025-08-14T20:54:40.7409479Z Complete job name: before-test / get-label-type / runner-determinator 2025-08-14T20:54:41.2972919Z ##[group]Run cat < runner_determinator.py 2025-08-14T20:54:41.2975556Z cat < runner_determinator.py 2025-08-14T20:54:41.2976276Z # flake8: noqa: G004 2025-08-14T20:54:41.2976829Z  2025-08-14T20:54:41.2977726Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:54:41.2978868Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:54:41.2979852Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:54:41.2980672Z  2025-08-14T20:54:41.2981121Z """ 2025-08-14T20:54:41.2982103Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:54:41.2983197Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:54:41.2984485Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:54:41.2985477Z of which runners should be used to run which job. 2025-08-14T20:54:41.2986270Z  2025-08-14T20:54:41.2987019Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:54:41.2988065Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:54:41.2989210Z settings are considered to be empty with only the second part, the user 2025-08-14T20:54:41.2990082Z list, defined. 2025-08-14T20:54:41.2990609Z  2025-08-14T20:54:41.2991654Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:54:41.2992774Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:54:41.2993800Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:54:41.2994616Z  2025-08-14T20:54:41.2995660Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:54:41.2996718Z The user list is also a comma separated list of additional features or 2025-08-14T20:54:41.2997700Z experiments which the user could be opted in to. 2025-08-14T20:54:41.2998418Z  2025-08-14T20:54:41.2998917Z The user list has the following rules: 2025-08-14T20:54:41.2999622Z  2025-08-14T20:54:41.3000345Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:54:41.3001709Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:54:41.3002752Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:54:41.3003414Z  2025-08-14T20:54:41.3003915Z Example config: 2025-08-14T20:54:41.3004561Z  # A list of experiments that can be opted into. 2025-08-14T20:54:41.3005508Z  # This defines the behavior they'll induce when opted into. 2025-08-14T20:54:41.3006321Z  # Expected syntax is: 2025-08-14T20:54:41.3007166Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:54:41.3008355Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:54:41.3009229Z  2025-08-14T20:54:41.3009754Z  experiments: 2025-08-14T20:54:41.3010333Z  lf: 2025-08-14T20:54:41.3010879Z  rollout_percent: 25 2025-08-14T20:54:41.3011692Z  all_branches: false 2025-08-14T20:54:41.3012299Z  default: true 2025-08-14T20:54:41.3012885Z  --- 2025-08-14T20:54:41.3013332Z  2025-08-14T20:54:41.3013911Z  # Opt-ins: 2025-08-14T20:54:41.3014672Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:54:41.3015906Z  # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:54:41.3016978Z  # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:54:41.3017794Z  # Experiments should be from the above list. 2025-08-14T20:54:41.3018514Z  2025-08-14T20:54:41.3019061Z  @User1,-lf,split_build 2025-08-14T20:54:41.3019687Z  @User2,lf 2025-08-14T20:54:41.3020214Z  @User3,split_build 2025-08-14T20:54:41.3104144Z """ 2025-08-14T20:54:41.3104938Z  2025-08-14T20:54:41.3105681Z import json 2025-08-14T20:54:41.3106490Z import logging 2025-08-14T20:54:41.3107317Z import os 2025-08-14T20:54:41.3108085Z import random 2025-08-14T20:54:41.3108982Z import re 2025-08-14T20:54:41.3109719Z import sys 2025-08-14T20:54:41.3110240Z from argparse import ArgumentParser 2025-08-14T20:54:41.3110929Z from collections.abc import Iterable 2025-08-14T20:54:41.3111824Z from functools import cache 2025-08-14T20:54:41.3112402Z from logging import LogRecord 2025-08-14T20:54:41.3112997Z from typing import Any, NamedTuple 2025-08-14T20:54:41.3113638Z from urllib.request import Request, urlopen 2025-08-14T20:54:41.3114249Z  2025-08-14T20:54:41.3114653Z import yaml 2025-08-14T20:54:41.3115128Z from github import Auth, Github 2025-08-14T20:54:41.3115711Z from github.Issue import Issue 2025-08-14T20:54:41.3116242Z  2025-08-14T20:54:41.3116616Z  2025-08-14T20:54:41.3117090Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:54:41.3117866Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:54:41.3118870Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:54:41.3119641Z  2025-08-14T20:54:41.3120365Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:54:41.3121013Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:54:41.3121945Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:54:41.3122613Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:54:41.3123195Z  2025-08-14T20:54:41.3123640Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:54:41.3124200Z  2025-08-14T20:54:41.3124625Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:54:41.3125175Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:54:41.3125683Z  2025-08-14T20:54:41.3126063Z  2025-08-14T20:54:41.3126500Z class Experiment(NamedTuple): 2025-08-14T20:54:41.3127068Z  rollout_perc: float = ( 2025-08-14T20:54:41.3127816Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:54:41.3128579Z  ) 2025-08-14T20:54:41.3129005Z  all_branches: bool = ( 2025-08-14T20:54:41.3129759Z  False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:54:41.3130498Z  ) 2025-08-14T20:54:41.3130929Z  default: bool = ( 2025-08-14T20:54:41.3131818Z  True # If True, the experiment is enabled by default for all queries 2025-08-14T20:54:41.3132523Z  ) 2025-08-14T20:54:41.3132933Z  2025-08-14T20:54:41.3133356Z  # Add more fields as needed 2025-08-14T20:54:41.3133900Z  2025-08-14T20:54:41.3134276Z  2025-08-14T20:54:41.3134699Z class Settings(NamedTuple): 2025-08-14T20:54:41.3135219Z  """ 2025-08-14T20:54:41.3135765Z  Settings for the experiments that can be opted into. 2025-08-14T20:54:41.3136413Z  """ 2025-08-14T20:54:41.3136827Z  2025-08-14T20:54:41.3137283Z  experiments: dict[str, Experiment] = {} 2025-08-14T20:54:41.3137869Z  2025-08-14T20:54:41.3138404Z  2025-08-14T20:54:41.3138878Z class ColorFormatter(logging.Formatter): 2025-08-14T20:54:41.3139594Z  """Color codes the log messages based on the log level""" 2025-08-14T20:54:41.3140232Z  2025-08-14T20:54:41.3140629Z  COLORS = { 2025-08-14T20:54:41.3141119Z  "WARNING": "\033[33m", # Yellow 2025-08-14T20:54:41.3141920Z  "ERROR": "\033[31m", # Red 2025-08-14T20:54:41.3142500Z  "CRITICAL": "\033[31m", # Red 2025-08-14T20:54:41.3143071Z  "INFO": "\033[0m", # Reset 2025-08-14T20:54:41.3143631Z  "DEBUG": "\033[0m", # Reset 2025-08-14T20:54:41.3144163Z  } 2025-08-14T20:54:41.3144566Z  2025-08-14T20:54:41.3145037Z  def format(self, record: LogRecord) -> str: 2025-08-14T20:54:41.3145895Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:54:41.3146760Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:54:41.3147409Z  return super().format(record) 2025-08-14T20:54:41.3147963Z  2025-08-14T20:54:41.3148354Z  2025-08-14T20:54:41.3148790Z handler = logging.StreamHandler() 2025-08-14T20:54:41.3149599Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:54:41.3150388Z  2025-08-14T20:54:41.3150897Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:54:41.3151782Z log.addHandler(handler) 2025-08-14T20:54:41.3152321Z log.setLevel(logging.INFO) 2025-08-14T20:54:41.3152840Z  2025-08-14T20:54:41.3153220Z  2025-08-14T20:54:41.3153727Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:54:41.3154375Z  """ 2025-08-14T20:54:41.3154967Z  Defines outputs of the github action that invokes this script 2025-08-14T20:54:41.3155826Z  """ 2025-08-14T20:54:41.3156266Z  if not GITHUB_OUTPUT: 2025-08-14T20:54:41.3157443Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:54:41.3158670Z  log.warning( 2025-08-14T20:54:41.3159636Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:54:41.3160650Z  ) 2025-08-14T20:54:41.3161928Z  print(f"::set-output name={key}::{value}") 2025-08-14T20:54:41.3162563Z  return 2025-08-14T20:54:41.3163009Z  2025-08-14T20:54:41.3163443Z  with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:54:41.3164101Z  log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:54:41.3164738Z  f.write(f"{key}={value}\n") 2025-08-14T20:54:41.3165294Z  2025-08-14T20:54:41.3165672Z  2025-08-14T20:54:41.3166236Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:54:41.3166969Z  return frozenset( 2025-08-14T20:54:41.3167676Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:54:41.3168421Z  ) 2025-08-14T20:54:41.3168844Z  2025-08-14T20:54:41.3169238Z  2025-08-14T20:54:41.3169652Z def parse_args() -> Any: 2025-08-14T20:54:41.3170332Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:54:41.3171444Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:54:41.3172300Z  parser.add_argument( 2025-08-14T20:54:41.3172856Z  "--github-issue-repo", 2025-08-14T20:54:41.3173400Z  type=str, 2025-08-14T20:54:41.3173898Z  required=False, 2025-08-14T20:54:41.3174608Z  default="pytorch/test-infra", 2025-08-14T20:54:41.3175249Z  help="GitHub repo to get the issue", 2025-08-14T20:54:41.3175824Z  ) 2025-08-14T20:54:41.3176252Z  parser.add_argument( 2025-08-14T20:54:41.3176788Z  "--github-repo", 2025-08-14T20:54:41.3177304Z  type=str, 2025-08-14T20:54:41.3177791Z  required=True, 2025-08-14T20:54:41.3178357Z  help="GitHub repo where CI is running", 2025-08-14T20:54:41.3178934Z  ) 2025-08-14T20:54:41.3179359Z  parser.add_argument( 2025-08-14T20:54:41.3180062Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:54:41.3180791Z  ) 2025-08-14T20:54:41.3181319Z  parser.add_argument( 2025-08-14T20:54:41.3182048Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:54:41.3182802Z  ) 2025-08-14T20:54:41.3183235Z  parser.add_argument( 2025-08-14T20:54:41.3183977Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:54:41.3184735Z  ) 2025-08-14T20:54:41.3185172Z  parser.add_argument( 2025-08-14T20:54:41.3185944Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:54:41.3186733Z  ) 2025-08-14T20:54:41.3187181Z  parser.add_argument( 2025-08-14T20:54:41.3187752Z  "--github-ref-type", 2025-08-14T20:54:41.3188287Z  type=str, 2025-08-14T20:54:41.3188776Z  required=True, 2025-08-14T20:54:41.3189380Z  help="Current GitHub ref type, branch or tag", 2025-08-14T20:54:41.3189997Z  ) 2025-08-14T20:54:41.3190419Z  parser.add_argument( 2025-08-14T20:54:41.3191104Z  "--eligible-experiments", 2025-08-14T20:54:41.3192033Z  type=_str_comma_separated_to_set, 2025-08-14T20:54:41.3192624Z  required=False, 2025-08-14T20:54:41.3193127Z  default="", 2025-08-14T20:54:41.3194093Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:54:41.3195105Z  ) 2025-08-14T20:54:41.3195537Z  parser.add_argument( 2025-08-14T20:54:41.3196090Z  "--opt-out-experiments", 2025-08-14T20:54:41.3196687Z  type=_str_comma_separated_to_set, 2025-08-14T20:54:41.3197272Z  required=False, 2025-08-14T20:54:41.3197782Z  default="", 2025-08-14T20:54:41.3198263Z  help=( 2025-08-14T20:54:41.3199038Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:54:41.3200271Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:54:41.3201323Z  ), 2025-08-14T20:54:41.3201749Z  ) 2025-08-14T20:54:41.3202179Z  parser.add_argument( 2025-08-14T20:54:41.3202699Z  "--pr-number", 2025-08-14T20:54:41.3203206Z  type=str, 2025-08-14T20:54:41.3203689Z  required=False, 2025-08-14T20:54:41.3204187Z  default="", 2025-08-14T20:54:41.3204765Z  help="the optional PR number where this is run", 2025-08-14T20:54:41.3205416Z  ) 2025-08-14T20:54:41.3205819Z  2025-08-14T20:54:41.3206234Z  return parser.parse_args() 2025-08-14T20:54:41.3206911Z  2025-08-14T20:54:41.3207361Z  2025-08-14T20:54:41.3208025Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:54:41.3209033Z  auth = Auth.Token(github_token) 2025-08-14T20:54:41.3209668Z  return Github(auth=auth) 2025-08-14T20:54:41.3210191Z  2025-08-14T20:54:41.3210564Z  2025-08-14T20:54:41.3211405Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:54:41.3212299Z  repo = gh.get_repo(repo) 2025-08-14T20:54:41.3212902Z  return repo.get_issue(number=issue_num) 2025-08-14T20:54:41.3213493Z  2025-08-14T20:54:41.3213866Z  2025-08-14T20:54:41.3214276Z def get_potential_pr_author( 2025-08-14T20:54:41.3215181Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:54:41.3215935Z ) -> str: 2025-08-14T20:54:41.3216540Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:54:41.3217494Z  # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:54:41.3218580Z  # embedded in the tag name: ciflow// 2025-08-14T20:54:41.3219223Z  2025-08-14T20:54:41.3219654Z  gh = get_gh_client(github_token) 2025-08-14T20:54:41.3220208Z  2025-08-14T20:54:41.3220734Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:54:41.3221542Z  split_tag = ref_name.split("/") 2025-08-14T20:54:41.3222110Z  if ( 2025-08-14T20:54:41.3222568Z  len(split_tag) == 3 2025-08-14T20:54:41.3223138Z  and split_tag[0] == "ciflow" 2025-08-14T20:54:41.3223741Z  and split_tag[2].isnumeric() 2025-08-14T20:54:41.3224292Z  ): 2025-08-14T20:54:41.3224762Z  pr_number = split_tag[2] 2025-08-14T20:54:41.3225322Z  try: 2025-08-14T20:54:41.3225843Z  repository = gh.get_repo(repo) 2025-08-14T20:54:41.3226699Z  pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:54:41.3227384Z  except Exception as e: 2025-08-14T20:54:41.3228010Z  raise Exception( # noqa: TRY002 2025-08-14T20:54:41.3228756Z  f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:54:41.3229478Z  ) from e 2025-08-14T20:54:41.3230113Z  return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:54:41.3230906Z  # In all other cases, return the original input username 2025-08-14T20:54:41.3231676Z  return username 2025-08-14T20:54:41.3232143Z  2025-08-14T20:54:41.3232525Z  2025-08-14T20:54:41.3232997Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:54:41.3233603Z  """ 2025-08-14T20:54:41.3234344Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:54:41.3235204Z  """ 2025-08-14T20:54:41.3235827Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:54:41.3236557Z  2025-08-14T20:54:41.3236939Z  2025-08-14T20:54:41.3237369Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:54:41.3237938Z  try: 2025-08-14T20:54:41.3238398Z  data = yaml.safe_load(yaml_text) 2025-08-14T20:54:41.3238977Z  return data 2025-08-14T20:54:41.3239475Z  except yaml.YAMLError: 2025-08-14T20:54:41.3240054Z  log.exception("Error loading YAML") 2025-08-14T20:54:41.3240628Z  raise 2025-08-14T20:54:41.3241075Z  2025-08-14T20:54:41.3241549Z  2025-08-14T20:54:41.3242217Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:54:41.3243039Z  """ 2025-08-14T20:54:41.3243881Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:54:41.3244711Z  2025-08-14T20:54:41.3245304Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:54:41.3246181Z  and the text below is the list of opted in users. 2025-08-14T20:54:41.3246808Z  2025-08-14T20:54:41.3247441Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:54:41.3248211Z  """ 2025-08-14T20:54:41.3248731Z  rollout_state_parts = rollout_state.split("---") 2025-08-14T20:54:41.3249395Z  if len(rollout_state_parts) >= 2: 2025-08-14T20:54:41.3250081Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:54:41.3250742Z  else: 2025-08-14T20:54:41.3251456Z  return "", rollout_state 2025-08-14T20:54:41.3252071Z  2025-08-14T20:54:41.3252448Z  2025-08-14T20:54:41.3252889Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:54:41.3253463Z  """ 2025-08-14T20:54:41.3254054Z  Dictionary of users with a list of features they have opted into 2025-08-14T20:54:41.3254771Z  """ 2025-08-14T20:54:41.3255170Z  2025-08-14T20:54:41.3255551Z  2025-08-14T20:54:41.3256139Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:54:41.3256864Z  """ 2025-08-14T20:54:41.3257665Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:54:41.3258561Z  2025-08-14T20:54:41.3259439Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:54:41.3260530Z  - Example line: "@User1,lf,split_build" 2025-08-14T20:54:41.3261533Z  - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:54:41.3262236Z  2025-08-14T20:54:41.3262605Z  2025-08-14T20:54:41.3262983Z  """ 2025-08-14T20:54:41.3263415Z  optins = UserOptins() 2025-08-14T20:54:41.3264000Z  for user in user_optin_text.split("\n"): 2025-08-14T20:54:41.3264629Z  user = user.strip("\r\n\t -") 2025-08-14T20:54:41.3265260Z  if not user or not user.startswith("@"): 2025-08-14T20:54:41.3265883Z  # Not a valid user. Skip 2025-08-14T20:54:41.3266426Z  continue 2025-08-14T20:54:41.3266897Z  2025-08-14T20:54:41.3267275Z  if user: 2025-08-14T20:54:41.3267836Z  usr_name = user.split(",")[0].strip("@") 2025-08-14T20:54:41.3268598Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:54:41.3269302Z  2025-08-14T20:54:41.3269693Z  return optins 2025-08-14T20:54:41.3270155Z  2025-08-14T20:54:41.3270528Z  2025-08-14T20:54:41.3271063Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:54:41.3271849Z  """ 2025-08-14T20:54:41.3272316Z  Check if the experiment name is valid. 2025-08-14T20:54:41.3272894Z  A valid name: 2025-08-14T20:54:41.3273634Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:54:41.3274657Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:54:41.3275439Z  - Cannot contain spaces 2025-08-14T20:54:41.3275962Z  """ 2025-08-14T20:54:41.3276363Z  2025-08-14T20:54:41.3276861Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:54:41.3277651Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:54:41.3278433Z  2025-08-14T20:54:41.3278825Z  if valid: 2025-08-14T20:54:41.3279447Z  return True 2025-08-14T20:54:41.3279990Z  2025-08-14T20:54:41.3280377Z  log.error( 2025-08-14T20:54:41.3282046Z  f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-14T20:54:41.3283716Z  ) 2025-08-14T20:54:41.3284124Z  return False 2025-08-14T20:54:41.3284576Z  2025-08-14T20:54:41.3284952Z  2025-08-14T20:54:41.3285523Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:54:41.3286230Z  """ 2025-08-14T20:54:41.3286893Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:54:41.3287679Z  """ 2025-08-14T20:54:41.3288076Z  try: 2025-08-14T20:54:41.3288503Z  if settings_text: 2025-08-14T20:54:41.3289333Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:54:41.3290194Z  # for easy reading 2025-08-14T20:54:41.3291089Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:54:41.3292171Z  # the backtick character in shell commands. 2025-08-14T20:54:41.3292857Z  backtick = chr(96) # backtick character 2025-08-14T20:54:41.3293611Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:54:41.3294355Z  settings = load_yaml(settings_text) 2025-08-14T20:54:41.3294924Z  2025-08-14T20:54:41.3295573Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:54:41.3296530Z  experiments = {} 2025-08-14T20:54:41.3297043Z  2025-08-14T20:54:41.3297655Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:54:41.3298492Z  if not is_valid_experiment_name(exp_name): 2025-08-14T20:54:41.3299693Z  # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-14T20:54:41.3300835Z  continue 2025-08-14T20:54:41.3301437Z  2025-08-14T20:54:41.3301861Z  valid_settings = {} 2025-08-14T20:54:41.3302457Z  for setting in exp_settings: 2025-08-14T20:54:41.3303098Z  if setting not in Experiment._fields: 2025-08-14T20:54:41.3303729Z  log.warning( 2025-08-14T20:54:41.3304536Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:54:41.3305341Z  ) 2025-08-14T20:54:41.3305840Z  else: 2025-08-14T20:54:41.3306443Z  valid_settings[setting] = exp_settings[setting] 2025-08-14T20:54:41.3307069Z  2025-08-14T20:54:41.3307588Z  experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:54:41.3308299Z  return Settings(experiments) 2025-08-14T20:54:41.3308846Z  2025-08-14T20:54:41.3309247Z  except Exception: 2025-08-14T20:54:41.3309810Z  log.exception("Failed to parse settings") 2025-08-14T20:54:41.3310401Z  2025-08-14T20:54:41.3310793Z  return Settings() 2025-08-14T20:54:41.3311364Z  2025-08-14T20:54:41.3311736Z  2025-08-14T20:54:41.3312362Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:54:41.3313014Z  """ 2025-08-14T20:54:41.3313527Z  Parse settings, if any, from the rollout state. 2025-08-14T20:54:41.3314135Z  2025-08-14T20:54:41.3314720Z  If the issue body contains "---" then the text above that is the settings 2025-08-14T20:54:41.3315759Z  and the text below is the list of opted in users. 2025-08-14T20:54:41.3316388Z  2025-08-14T20:54:41.3317036Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:54:41.3317824Z  """ 2025-08-14T20:54:41.3318452Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:54:41.3319319Z  return parse_settings_from_text(settings_text) 2025-08-14T20:54:41.3319917Z  2025-08-14T20:54:41.3320285Z  2025-08-14T20:54:41.3320797Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:54:41.3321535Z  """ 2025-08-14T20:54:41.3321998Z  Parse users from the rollout state. 2025-08-14T20:54:41.3322556Z  2025-08-14T20:54:41.3322922Z  """ 2025-08-14T20:54:41.3323526Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:54:41.3324356Z  return parse_user_opt_in_from_text(users_text) 2025-08-14T20:54:41.3324955Z  2025-08-14T20:54:41.3325330Z  2025-08-14T20:54:41.3326014Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:54:41.3326837Z  """ 2025-08-14T20:54:41.3327329Z  Check if a user is opted into an experiment 2025-08-14T20:54:41.3327918Z  """ 2025-08-14T20:54:41.3328459Z  return experiment_name in user_optins.get(user, []) 2025-08-14T20:54:41.3329099Z  2025-08-14T20:54:41.3329620Z  2025-08-14T20:54:41.3330306Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:54:41.3331234Z  """ 2025-08-14T20:54:41.3331770Z  Check if a user explicitly opted out of an experiment 2025-08-14T20:54:41.3332415Z  """ 2025-08-14T20:54:41.3332996Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:54:41.3333768Z  experiment_optout = "-" + experiment_name 2025-08-14T20:54:41.3334494Z  if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:54:41.3335159Z  return False 2025-08-14T20:54:41.3335618Z  2025-08-14T20:54:41.3336144Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:54:41.3336799Z  log.warning( 2025-08-14T20:54:41.3337709Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:54:41.3338652Z  ) 2025-08-14T20:54:41.3339061Z  2025-08-14T20:54:41.3339442Z  return True 2025-08-14T20:54:41.3339889Z  2025-08-14T20:54:41.3340255Z  2025-08-14T20:54:41.3340656Z def get_runner_prefix( 2025-08-14T20:54:41.3341262Z  rollout_state: str, 2025-08-14T20:54:41.3341810Z  workflow_requestors: Iterable[str], 2025-08-14T20:54:41.3342383Z  branch: str, 2025-08-14T20:54:41.3342964Z  eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:54:41.3343722Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:54:41.3344361Z  is_canary: bool = False, 2025-08-14T20:54:41.3344879Z ) -> str: 2025-08-14T20:54:41.3345387Z  settings = parse_settings(rollout_state) 2025-08-14T20:54:41.3346035Z  user_optins = parse_users(rollout_state) 2025-08-14T20:54:41.3346616Z  2025-08-14T20:54:41.3347139Z  fleet_prefix = "" 2025-08-14T20:54:41.3347649Z  prefixes = [] 2025-08-14T20:54:41.3348377Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:54:41.3349411Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:54:41.3350197Z  log.info( 2025-08-14T20:54:41.3350965Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:54:41.3351895Z  ) 2025-08-14T20:54:41.3352357Z  continue 2025-08-14T20:54:41.3352818Z  2025-08-14T20:54:41.3353226Z  if opt_out_experiments: 2025-08-14T20:54:41.3353852Z  if experiment_name in opt_out_experiments: 2025-08-14T20:54:41.3354596Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:54:41.3355246Z  log.info( 2025-08-14T20:54:41.3356278Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:54:41.3357313Z  ) 2025-08-14T20:54:41.3357785Z  continue 2025-08-14T20:54:41.3358276Z  2025-08-14T20:54:41.3358721Z  if eligible_experiments: 2025-08-14T20:54:41.3359371Z  if experiment_name not in eligible_experiments: 2025-08-14T20:54:41.3360070Z  exp_list = ", ".join(eligible_experiments) 2025-08-14T20:54:41.3360678Z  log.info( 2025-08-14T20:54:41.3361647Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:54:41.3362556Z  ) 2025-08-14T20:54:41.3363028Z  continue 2025-08-14T20:54:41.3363718Z  elif not experiment_settings.default: 2025-08-14T20:54:41.3364308Z  log.info( 2025-08-14T20:54:41.3365060Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:54:41.3365868Z  ) 2025-08-14T20:54:41.3366308Z  continue 2025-08-14T20:54:41.3366774Z  2025-08-14T20:54:41.3367295Z  # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:54:41.3367968Z  opted_out_users = [ 2025-08-14T20:54:41.3368505Z  requestor 2025-08-14T20:54:41.3369041Z  for requestor in workflow_requestors 2025-08-14T20:54:41.3369793Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:54:41.3370472Z  ] 2025-08-14T20:54:41.3370886Z  2025-08-14T20:54:41.3371396Z  if opted_out_users: 2025-08-14T20:54:41.3371941Z  log.info( 2025-08-14T20:54:41.3372670Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:54:41.3373429Z  ) 2025-08-14T20:54:41.3373881Z  continue 2025-08-14T20:54:41.3374342Z  2025-08-14T20:54:41.3374858Z  # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:54:41.3375526Z  opted_in_users = [ 2025-08-14T20:54:41.3376068Z  requestor 2025-08-14T20:54:41.3376614Z  for requestor in workflow_requestors 2025-08-14T20:54:41.3377346Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:54:41.3378030Z  ] 2025-08-14T20:54:41.3378437Z  2025-08-14T20:54:41.3378827Z  enabled = False 2025-08-14T20:54:41.3379338Z  if opted_in_users: 2025-08-14T20:54:41.3379985Z  log.info( 2025-08-14T20:54:41.3380694Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:54:41.3381535Z  ) 2025-08-14T20:54:41.3381993Z  enabled = True 2025-08-14T20:54:41.3382489Z  2025-08-14T20:54:41.3382943Z  elif experiment_settings.rollout_perc: 2025-08-14T20:54:41.3383857Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:54:41.3384895Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:54:41.3385606Z  log.info( 2025-08-14T20:54:41.3386568Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:54:41.3387561Z  ) 2025-08-14T20:54:41.3388044Z  enabled = True 2025-08-14T20:54:41.3388561Z  2025-08-14T20:54:41.3388952Z  if enabled: 2025-08-14T20:54:41.3389463Z  label = experiment_name 2025-08-14T20:54:41.3390092Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:54:41.3390994Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:54:41.3392044Z  # - If it's enabled, then we always list it's prefix first 2025-08-14T20:54:41.3392885Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:54:41.3393615Z  if is_canary: 2025-08-14T20:54:41.3394191Z  label += CANARY_FLEET_SUFFIX 2025-08-14T20:54:41.3394791Z  fleet_prefix = label 2025-08-14T20:54:41.3395347Z  else: 2025-08-14T20:54:41.3395999Z  prefixes.append(label) 2025-08-14T20:54:41.3396559Z  2025-08-14T20:54:41.3396946Z  if len(prefixes) > 1: 2025-08-14T20:54:41.3397470Z  log.error( 2025-08-14T20:54:41.3398622Z  f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-14T20:54:41.3399802Z  ) 2025-08-14T20:54:41.3400257Z  prefixes = prefixes[:1] 2025-08-14T20:54:41.3400777Z  2025-08-14T20:54:41.3401286Z  # Fleet always comes first 2025-08-14T20:54:41.3401835Z  if fleet_prefix: 2025-08-14T20:54:41.3402377Z  prefixes.insert(0, fleet_prefix) 2025-08-14T20:54:41.3402939Z  2025-08-14T20:54:41.3403425Z  return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:54:41.3404045Z  2025-08-14T20:54:41.3404416Z  2025-08-14T20:54:41.3405139Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:54:41.3405974Z  """ 2025-08-14T20:54:41.3406631Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:54:41.3407385Z  2025-08-14T20:54:41.3408016Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:54:41.3408786Z  """ 2025-08-14T20:54:41.3409237Z  gh = get_gh_client(github_token) 2025-08-14T20:54:41.3409864Z  issue = get_issue(gh, repo, issue_num) 2025-08-14T20:54:41.3410573Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:54:41.3411328Z  2025-08-14T20:54:41.3411703Z  2025-08-14T20:54:41.3412360Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:54:41.3413316Z  for _ in range(num_retries): 2025-08-14T20:54:41.3413856Z  try: 2025-08-14T20:54:41.3414357Z  req = Request(url=url, headers=headers) 2025-08-14T20:54:41.3415083Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:54:41.3415801Z  return json.loads(content) 2025-08-14T20:54:41.3416459Z  except Exception as e: 2025-08-14T20:54:41.3417093Z  log.warning(f"Could not download {url}: {e}") 2025-08-14T20:54:41.3417704Z  2025-08-14T20:54:41.3418327Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:54:41.3419103Z  return {} 2025-08-14T20:54:41.3419539Z  2025-08-14T20:54:41.3419910Z  2025-08-14T20:54:41.3420282Z @cache 2025-08-14T20:54:41.3420991Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:54:41.3421937Z  """ 2025-08-14T20:54:41.3422395Z  Dynamically get PR information 2025-08-14T20:54:41.3422953Z  """ 2025-08-14T20:54:41.3423527Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:54:41.3424225Z  headers = { 2025-08-14T20:54:41.3424768Z  "Accept": "application/vnd.github.v3+json", 2025-08-14T20:54:41.3425450Z  "Authorization": f"token {github_token}", 2025-08-14T20:54:41.3426028Z  } 2025-08-14T20:54:41.3426519Z  json_response: dict[str, Any] = download_json( 2025-08-14T20:54:41.3427198Z  url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:54:41.3427800Z  headers=headers, 2025-08-14T20:54:41.3428290Z  ) 2025-08-14T20:54:41.3428681Z  2025-08-14T20:54:41.3429090Z  if not json_response: 2025-08-14T20:54:41.3429750Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:54:41.3430587Z  return {} 2025-08-14T20:54:41.3431043Z  2025-08-14T20:54:41.3431549Z  return json_response 2025-08-14T20:54:41.3432046Z  2025-08-14T20:54:41.3432410Z  2025-08-14T20:54:41.3433072Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:54:41.3433862Z  """ 2025-08-14T20:54:41.3434470Z  Dynamically get the latest list of labels from the pull request 2025-08-14T20:54:41.3435177Z  """ 2025-08-14T20:54:41.3435745Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:54:41.3436422Z  return { 2025-08-14T20:54:41.3437076Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:54:41.3437818Z  } 2025-08-14T20:54:41.3438208Z  2025-08-14T20:54:41.3438581Z  2025-08-14T20:54:41.3438980Z def main() -> None: 2025-08-14T20:54:41.3439480Z  args = parse_args() 2025-08-14T20:54:41.3439967Z  2025-08-14T20:54:41.3440428Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:54:41.3441019Z  2025-08-14T20:54:41.3441518Z  # Check if the PR is opt-out 2025-08-14T20:54:41.3442086Z  if args.pr_number: 2025-08-14T20:54:41.3442835Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:54:41.3443658Z  if OPT_OUT_LABEL in labels: 2025-08-14T20:54:41.3444205Z  log.info( 2025-08-14T20:54:41.3444990Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:54:41.3445817Z  ) 2025-08-14T20:54:41.3446458Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:54:41.3447201Z  sys.exit() 2025-08-14T20:54:41.3447795Z  2025-08-14T20:54:41.3448182Z  try: 2025-08-14T20:54:41.3448676Z  rollout_state = get_rollout_state_from_issue( 2025-08-14T20:54:41.3449467Z  args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:54:41.3450169Z  ) 2025-08-14T20:54:41.3450577Z  2025-08-14T20:54:41.3451023Z  username = get_potential_pr_author( 2025-08-14T20:54:41.3451716Z  args.github_token, 2025-08-14T20:54:41.3452267Z  args.github_repo, 2025-08-14T20:54:41.3452804Z  args.github_actor, 2025-08-14T20:54:41.3453358Z  args.github_ref_type, 2025-08-14T20:54:41.3453919Z  args.github_branch, 2025-08-14T20:54:41.3454451Z  ) 2025-08-14T20:54:41.3454856Z  2025-08-14T20:54:41.3455381Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:54:41.3456048Z  2025-08-14T20:54:41.3456503Z  runner_label_prefix = get_runner_prefix( 2025-08-14T20:54:41.3457109Z  rollout_state, 2025-08-14T20:54:41.3457676Z  (args.github_issue_owner, username), 2025-08-14T20:54:41.3458279Z  args.github_branch, 2025-08-14T20:54:41.3458860Z  args.eligible_experiments, 2025-08-14T20:54:41.3459461Z  args.opt_out_experiments, 2025-08-14T20:54:41.3460024Z  is_canary, 2025-08-14T20:54:41.3460501Z  ) 2025-08-14T20:54:41.3460921Z  2025-08-14T20:54:41.3461425Z  except Exception as e: 2025-08-14T20:54:41.3461949Z  log.error( 2025-08-14T20:54:41.3462724Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:54:41.3463538Z  ) 2025-08-14T20:54:41.3464086Z  2025-08-14T20:54:41.3464660Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:54:41.3465378Z  2025-08-14T20:54:41.3465747Z  2025-08-14T20:54:41.3466144Z if __name__ == "__main__": 2025-08-14T20:54:41.3466644Z  main() 2025-08-14T20:54:41.3467067Z  2025-08-14T20:54:41.3467438Z EOF 2025-08-14T20:54:41.3467829Z  2025-08-14T20:54:41.3468233Z cat runner_determinator.py 2025-08-14T20:54:41.3888988Z shell: /usr/bin/bash -e {0} 2025-08-14T20:54:41.3889824Z env: 2025-08-14T20:54:41.3890554Z GITHUB_TOKEN: *** 2025-08-14T20:54:41.3890986Z ISSUE_NUMBER: 5132 2025-08-14T20:54:41.3891744Z TRIGGERING_ACTOR: huydhn 2025-08-14T20:54:41.3892213Z ISSUE_OWNER: 2025-08-14T20:54:41.3892625Z CHECK_EXPERIMENTS: 2025-08-14T20:54:41.3893062Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:54:41.3893498Z PR_NUMBER: 2025-08-14T20:54:41.3893899Z ##[endgroup] 2025-08-14T20:54:41.4113382Z # flake8: noqa: G004 2025-08-14T20:54:41.4113923Z 2025-08-14T20:54:41.4114568Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-08-14T20:54:41.4115535Z # must be kept in sync. You can do it easily by running the following command: 2025-08-14T20:54:41.4116340Z # python .github/scripts/update_runner_determinator.py 2025-08-14T20:54:41.4116787Z 2025-08-14T20:54:41.4116956Z """ 2025-08-14T20:54:41.4117534Z This runner determinator is used to determine which set of runners to run a 2025-08-14T20:54:41.4118420Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-08-14T20:54:41.4119310Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-08-14T20:54:41.4120137Z of which runners should be used to run which job. 2025-08-14T20:54:41.4120536Z 2025-08-14T20:54:41.4120928Z The configuration has two parts, the settings and a list of opted-in users, 2025-08-14T20:54:41.4122369Z separated by a line containing "---". If the line is not present, the 2025-08-14T20:54:41.4123305Z settings are considered to be empty with only the second part, the user 2025-08-14T20:54:41.4124015Z list, defined. 2025-08-14T20:54:41.4124250Z 2025-08-14T20:54:41.4124618Z The first part is a YAML block that defines the rollout settings. This can be 2025-08-14T20:54:41.4125558Z used to define any settings that are needed to determine which runners to use. 2025-08-14T20:54:41.4126388Z It's fields are defined by the RolloutSettings class below. 2025-08-14T20:54:41.4126831Z 2025-08-14T20:54:41.4127210Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-08-14T20:54:41.4128067Z The user list is also a comma separated list of additional features or 2025-08-14T20:54:41.4128815Z experiments which the user could be opted in to. 2025-08-14T20:54:41.4129216Z 2025-08-14T20:54:41.4129413Z The user list has the following rules: 2025-08-14T20:54:41.4129764Z 2025-08-14T20:54:41.4130085Z - Users are GitHub usernames, which must start with the @ prefix 2025-08-14T20:54:41.4130927Z - Each user is also a comma-separated list of features/experiments to enable 2025-08-14T20:54:41.4131927Z - A "#" prefix opts the user out of all experiments 2025-08-14T20:54:41.4132333Z 2025-08-14T20:54:41.4132509Z Example config: 2025-08-14T20:54:41.4132956Z # A list of experiments that can be opted into. 2025-08-14T20:54:41.4133627Z # This defines the behavior they'll induce when opted into. 2025-08-14T20:54:41.4134244Z # Expected syntax is: 2025-08-14T20:54:41.4134887Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-08-14T20:54:41.4135861Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-08-14T20:54:41.4136479Z 2025-08-14T20:54:41.4136655Z experiments: 2025-08-14T20:54:41.4137049Z lf: 2025-08-14T20:54:41.4137424Z rollout_percent: 25 2025-08-14T20:54:41.4137914Z all_branches: false 2025-08-14T20:54:41.4138513Z default: true 2025-08-14T20:54:41.4138919Z --- 2025-08-14T20:54:41.4139127Z 2025-08-14T20:54:41.4139288Z # Opt-ins: 2025-08-14T20:54:41.4139863Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-08-14T20:54:41.4140736Z # and specifying experiments to enable in a comma-separated list. 2025-08-14T20:54:41.4142131Z # To always opt out of an experiment, prefix it with a "-". 2025-08-14T20:54:41.4142799Z # Experiments should be from the above list. 2025-08-14T20:54:41.4143178Z 2025-08-14T20:54:41.4143356Z @User1,-lf,split_build 2025-08-14T20:54:41.4143795Z @User2,lf 2025-08-14T20:54:41.4144177Z @User3,split_build 2025-08-14T20:54:41.4144581Z """ 2025-08-14T20:54:41.4144774Z 2025-08-14T20:54:41.4144937Z import json 2025-08-14T20:54:41.4145311Z import logging 2025-08-14T20:54:41.4145695Z import os 2025-08-14T20:54:41.4146063Z import random 2025-08-14T20:54:41.4146438Z import re 2025-08-14T20:54:41.4146812Z import sys 2025-08-14T20:54:41.4147218Z from argparse import ArgumentParser 2025-08-14T20:54:41.4147753Z from collections.abc import Iterable 2025-08-14T20:54:41.4148276Z from functools import cache 2025-08-14T20:54:41.4148741Z from logging import LogRecord 2025-08-14T20:54:41.4149224Z from typing import Any, NamedTuple 2025-08-14T20:54:41.4149749Z from urllib.request import Request, urlopen 2025-08-14T20:54:41.4150127Z 2025-08-14T20:54:41.4150292Z import yaml 2025-08-14T20:54:41.4150673Z from github import Auth, Github 2025-08-14T20:54:41.4151365Z from github.Issue import Issue 2025-08-14T20:54:41.4151687Z 2025-08-14T20:54:41.4151694Z 2025-08-14T20:54:41.4151915Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-08-14T20:54:41.4152600Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-08-14T20:54:41.4153489Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-08-14T20:54:41.4154050Z 2025-08-14T20:54:41.4154275Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-08-14T20:54:41.4155001Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-08-14T20:54:41.4155520Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-08-14T20:54:41.4156065Z OPT_OUT_LABEL = "no-runner-experiments" 2025-08-14T20:54:41.4156420Z 2025-08-14T20:54:41.4156618Z SETTING_EXPERIMENTS = "experiments" 2025-08-14T20:54:41.4156952Z 2025-08-14T20:54:41.4157135Z LF_FLEET_EXPERIMENT = "lf" 2025-08-14T20:54:41.4157595Z CANARY_FLEET_SUFFIX = ".c" 2025-08-14T20:54:41.4157876Z 2025-08-14T20:54:41.4157882Z 2025-08-14T20:54:41.4158065Z class Experiment(NamedTuple): 2025-08-14T20:54:41.4158550Z rollout_perc: float = ( 2025-08-14T20:54:41.4159187Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-08-14T20:54:41.4159871Z ) 2025-08-14T20:54:41.4160232Z all_branches: bool = ( 2025-08-14T20:54:41.4160845Z False # If True, the experiment is also enabled on the exception branches 2025-08-14T20:54:41.4161757Z ) 2025-08-14T20:54:41.4162116Z default: bool = ( 2025-08-14T20:54:41.4162699Z True # If True, the experiment is enabled by default for all queries 2025-08-14T20:54:41.4163336Z ) 2025-08-14T20:54:41.4163530Z 2025-08-14T20:54:41.4163709Z # Add more fields as needed 2025-08-14T20:54:41.4164003Z 2025-08-14T20:54:41.4164009Z 2025-08-14T20:54:41.4164198Z class Settings(NamedTuple): 2025-08-14T20:54:41.4164649Z """ 2025-08-14T20:54:41.4165417Z Settings for the experiments that can be opted into. 2025-08-14T20:54:41.4165992Z """ 2025-08-14T20:54:41.4166185Z 2025-08-14T20:54:41.4166400Z experiments: dict[str, Experiment] = {} 2025-08-14T20:54:41.4166761Z 2025-08-14T20:54:41.4166769Z 2025-08-14T20:54:41.4166974Z class ColorFormatter(logging.Formatter): 2025-08-14T20:54:41.4167625Z """Color codes the log messages based on the log level""" 2025-08-14T20:54:41.4168055Z 2025-08-14T20:54:41.4168221Z COLORS = { 2025-08-14T20:54:41.4168613Z "WARNING": "\033[33m", # Yellow 2025-08-14T20:54:41.4169112Z "ERROR": "\033[31m", # Red 2025-08-14T20:54:41.4169770Z "CRITICAL": "\033[31m", # Red 2025-08-14T20:54:41.4170280Z "INFO": "\033[0m", # Reset 2025-08-14T20:54:41.4170790Z "DEBUG": "\033[0m", # Reset 2025-08-14T20:54:41.4171538Z } 2025-08-14T20:54:41.4171743Z 2025-08-14T20:54:41.4171955Z def format(self, record: LogRecord) -> str: 2025-08-14T20:54:41.4172697Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-08-14T20:54:41.4173467Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-08-14T20:54:41.4174041Z return super().format(record) 2025-08-14T20:54:41.4174373Z 2025-08-14T20:54:41.4174380Z 2025-08-14T20:54:41.4174577Z handler = logging.StreamHandler() 2025-08-14T20:54:41.4175275Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-08-14T20:54:41.4175844Z 2025-08-14T20:54:41.4176082Z log = logging.getLogger(os.path.basename(__file__)) 2025-08-14T20:54:41.4176650Z log.addHandler(handler) 2025-08-14T20:54:41.4177104Z log.setLevel(logging.INFO) 2025-08-14T20:54:41.4177388Z 2025-08-14T20:54:41.4177395Z 2025-08-14T20:54:41.4177644Z def set_github_output(key: str, value: str) -> None: 2025-08-14T20:54:41.4178200Z """ 2025-08-14T20:54:41.4178700Z Defines outputs of the github action that invokes this script 2025-08-14T20:54:41.4179330Z """ 2025-08-14T20:54:41.4179692Z if not GITHUB_OUTPUT: 2025-08-14T20:54:41.4180756Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-08-14T20:54:41.4182096Z log.warning( 2025-08-14T20:54:41.4182950Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-08-14T20:54:41.4183898Z ) 2025-08-14T20:54:41.4194101Z print(f"::set-output name={key}::{value}") 2025-08-14T20:54:41.4194721Z return 2025-08-14T20:54:41.4194957Z 2025-08-14T20:54:41.4195171Z with open(GITHUB_OUTPUT, "a") as f: 2025-08-14T20:54:41.4195908Z log.info(f"Setting output: {key}='{value}'") 2025-08-14T20:54:41.4196524Z f.write(f"{key}={value}\n") 2025-08-14T20:54:41.4196850Z 2025-08-14T20:54:41.4196858Z 2025-08-14T20:54:41.4197169Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-08-14T20:54:41.4197804Z return frozenset( 2025-08-14T20:54:41.4198417Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-08-14T20:54:41.4199110Z ) 2025-08-14T20:54:41.4199308Z 2025-08-14T20:54:41.4199315Z 2025-08-14T20:54:41.4199490Z def parse_args() -> Any: 2025-08-14T20:54:41.4200047Z parser = ArgumentParser("Get dynamic rollout settings") 2025-08-14T20:54:41.4200925Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-08-14T20:54:41.4201979Z parser.add_argument( 2025-08-14T20:54:41.4202447Z "--github-issue-repo", 2025-08-14T20:54:41.4202903Z type=str, 2025-08-14T20:54:41.4203314Z required=False, 2025-08-14T20:54:41.4203769Z default="pytorch/test-infra", 2025-08-14T20:54:41.4204303Z help="GitHub repo to get the issue", 2025-08-14T20:54:41.4204810Z ) 2025-08-14T20:54:41.4205169Z parser.add_argument( 2025-08-14T20:54:41.4205616Z "--github-repo", 2025-08-14T20:54:41.4206040Z type=str, 2025-08-14T20:54:41.4206431Z required=True, 2025-08-14T20:54:41.4206883Z help="GitHub repo where CI is running", 2025-08-14T20:54:41.4207410Z ) 2025-08-14T20:54:41.4207778Z parser.add_argument( 2025-08-14T20:54:41.4208377Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-08-14T20:54:41.4209036Z ) 2025-08-14T20:54:41.4209392Z parser.add_argument( 2025-08-14T20:54:41.4210012Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-08-14T20:54:41.4210690Z ) 2025-08-14T20:54:41.4211050Z parser.add_argument( 2025-08-14T20:54:41.4212057Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-08-14T20:54:41.4212766Z ) 2025-08-14T20:54:41.4213134Z parser.add_argument( 2025-08-14T20:54:41.4213774Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-08-14T20:54:41.4214496Z ) 2025-08-14T20:54:41.4214872Z parser.add_argument( 2025-08-14T20:54:41.4215323Z "--github-ref-type", 2025-08-14T20:54:41.4215771Z type=str, 2025-08-14T20:54:41.4216165Z required=True, 2025-08-14T20:54:41.4216639Z help="Current GitHub ref type, branch or tag", 2025-08-14T20:54:41.4217193Z ) 2025-08-14T20:54:41.4217554Z parser.add_argument( 2025-08-14T20:54:41.4218008Z "--eligible-experiments", 2025-08-14T20:54:41.4218514Z type=_str_comma_separated_to_set, 2025-08-14T20:54:41.4269448Z required=False, 2025-08-14T20:54:41.4269977Z default="", 2025-08-14T20:54:41.4270956Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-08-14T20:54:41.4272048Z ) 2025-08-14T20:54:41.4272432Z parser.add_argument( 2025-08-14T20:54:41.4272896Z "--opt-out-experiments", 2025-08-14T20:54:41.4273394Z type=_str_comma_separated_to_set, 2025-08-14T20:54:41.4273906Z required=False, 2025-08-14T20:54:41.4274326Z default="", 2025-08-14T20:54:41.4274725Z help=( 2025-08-14T20:54:41.4275395Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-08-14T20:54:41.4276549Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-08-14T20:54:41.4277384Z ), 2025-08-14T20:54:41.4277727Z ) 2025-08-14T20:54:41.4278093Z parser.add_argument( 2025-08-14T20:54:41.4278522Z "--pr-number", 2025-08-14T20:54:41.4278931Z type=str, 2025-08-14T20:54:41.4279317Z required=False, 2025-08-14T20:54:41.4279743Z default="", 2025-08-14T20:54:41.4280499Z help="the optional PR number where this is run", 2025-08-14T20:54:41.4281070Z ) 2025-08-14T20:54:41.4281429Z 2025-08-14T20:54:41.4281617Z return parser.parse_args() 2025-08-14T20:54:41.4281951Z 2025-08-14T20:54:41.4281958Z 2025-08-14T20:54:41.4282359Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-08-14T20:54:41.4283117Z auth = Auth.Token(github_token) 2025-08-14T20:54:41.4283606Z return Github(auth=auth) 2025-08-14T20:54:41.4283907Z 2025-08-14T20:54:41.4283915Z 2025-08-14T20:54:41.4284372Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-08-14T20:54:41.4285183Z repo = gh.get_repo(repo) 2025-08-14T20:54:41.4285678Z return repo.get_issue(number=issue_num) 2025-08-14T20:54:41.4286038Z 2025-08-14T20:54:41.4286045Z 2025-08-14T20:54:41.4286235Z def get_potential_pr_author( 2025-08-14T20:54:41.4286875Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-08-14T20:54:41.4287555Z ) -> str: 2025-08-14T20:54:41.4288058Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-08-14T20:54:41.4288849Z # Fetch the actual username from the original PR. The PR number is 2025-08-14T20:54:41.4289583Z # embedded in the tag name: ciflow// 2025-08-14T20:54:41.4290001Z 2025-08-14T20:54:41.4290187Z gh = get_gh_client(github_token) 2025-08-14T20:54:41.4290518Z 2025-08-14T20:54:41.4290781Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-08-14T20:54:41.4291508Z split_tag = ref_name.split("/") 2025-08-14T20:54:41.4292008Z if ( 2025-08-14T20:54:41.4292383Z len(split_tag) == 3 2025-08-14T20:54:41.4292859Z and split_tag[0] == "ciflow" 2025-08-14T20:54:41.4293373Z and split_tag[2].isnumeric() 2025-08-14T20:54:41.4293867Z ): 2025-08-14T20:54:41.4294236Z pr_number = split_tag[2] 2025-08-14T20:54:41.4294862Z try: 2025-08-14T20:54:41.4295290Z repository = gh.get_repo(repo) 2025-08-14T20:54:41.4295895Z pull = repository.get_pull(number=int(pr_number)) 2025-08-14T20:54:41.4296491Z except Exception as e: 2025-08-14T20:54:41.4296999Z raise Exception( # noqa: TRY002 2025-08-14T20:54:41.4297659Z f"issue with pull request {pr_number} from repo {repository}" 2025-08-14T20:54:41.4298292Z ) from e 2025-08-14T20:54:41.4298822Z return pull.user.login # type: ignore[no-any-return] 2025-08-14T20:54:41.4299513Z # In all other cases, return the original input username 2025-08-14T20:54:41.4300099Z return username 2025-08-14T20:54:41.4300339Z 2025-08-14T20:54:41.4300345Z 2025-08-14T20:54:41.4300570Z def is_exception_branch(branch: str) -> bool: 2025-08-14T20:54:41.4301095Z """ 2025-08-14T20:54:41.4301837Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-08-14T20:54:41.4302617Z """ 2025-08-14T20:54:41.4303156Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-08-14T20:54:41.4303676Z 2025-08-14T20:54:41.4303683Z 2025-08-14T20:54:41.4303873Z def load_yaml(yaml_text: str) -> Any: 2025-08-14T20:54:41.4304355Z try: 2025-08-14T20:54:41.4304737Z data = yaml.safe_load(yaml_text) 2025-08-14T20:54:41.4305235Z return data 2025-08-14T20:54:41.4305645Z except yaml.YAMLError: 2025-08-14T20:54:41.4306113Z log.exception("Error loading YAML") 2025-08-14T20:54:41.4306618Z raise 2025-08-14T20:54:41.4306834Z 2025-08-14T20:54:41.4306841Z 2025-08-14T20:54:41.4307259Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-08-14T20:54:41.4308010Z """ 2025-08-14T20:54:41.4308620Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-08-14T20:54:41.4309225Z 2025-08-14T20:54:41.4309723Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:54:41.4310491Z and the text below is the list of opted in users. 2025-08-14T20:54:41.4310890Z 2025-08-14T20:54:41.4311369Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-08-14T20:54:41.4312071Z """ 2025-08-14T20:54:41.4312499Z rollout_state_parts = rollout_state.split("---") 2025-08-14T20:54:41.4313111Z if len(rollout_state_parts) >= 2: 2025-08-14T20:54:41.4313707Z return rollout_state_parts[0], rollout_state_parts[1] 2025-08-14T20:54:41.4314285Z else: 2025-08-14T20:54:41.4314660Z return "", rollout_state 2025-08-14T20:54:41.4314964Z 2025-08-14T20:54:41.4314971Z 2025-08-14T20:54:41.4315165Z class UserOptins(dict[str, list[str]]): 2025-08-14T20:54:41.4315661Z """ 2025-08-14T20:54:41.4316167Z Dictionary of users with a list of features they have opted into 2025-08-14T20:54:41.4316814Z """ 2025-08-14T20:54:41.4317003Z 2025-08-14T20:54:41.4317015Z 2025-08-14T20:54:41.4317364Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-08-14T20:54:41.4318012Z """ 2025-08-14T20:54:41.4318716Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-08-14T20:54:41.4319396Z 2025-08-14T20:54:41.4320014Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-08-14T20:54:41.4321006Z - Example line: "@User1,lf,split_build" 2025-08-14T20:54:41.4321824Z - A "#" prefix indicates the user is opted out of all experiments 2025-08-14T20:54:41.4322319Z 2025-08-14T20:54:41.4322325Z 2025-08-14T20:54:41.4322481Z """ 2025-08-14T20:54:41.4322846Z optins = UserOptins() 2025-08-14T20:54:41.4323319Z for user in user_optin_text.split("\n"): 2025-08-14T20:54:41.4323866Z user = user.strip("\r\n\t -") 2025-08-14T20:54:41.4324391Z if not user or not user.startswith("@"): 2025-08-14T20:54:41.4325088Z # Not a valid user. Skip 2025-08-14T20:54:41.4325562Z continue 2025-08-14T20:54:41.4325807Z 2025-08-14T20:54:41.4325962Z if user: 2025-08-14T20:54:41.4326385Z usr_name = user.split(",")[0].strip("@") 2025-08-14T20:54:41.4327072Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-08-14T20:54:41.4327578Z 2025-08-14T20:54:41.4327744Z return optins 2025-08-14T20:54:41.4327977Z 2025-08-14T20:54:41.4327983Z 2025-08-14T20:54:41.4328259Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-08-14T20:54:41.4328856Z """ 2025-08-14T20:54:41.4329234Z Check if the experiment name is valid. 2025-08-14T20:54:41.4329754Z A valid name: 2025-08-14T20:54:41.4330376Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-08-14T20:54:41.4331483Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-08-14T20:54:41.4332215Z - Cannot contain spaces 2025-08-14T20:54:41.4332677Z """ 2025-08-14T20:54:41.4332871Z 2025-08-14T20:54:41.4333132Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-08-14T20:54:41.4333821Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-08-14T20:54:41.4334460Z 2025-08-14T20:54:41.4334626Z if valid: 2025-08-14T20:54:41.4334998Z return True 2025-08-14T20:54:41.4335244Z 2025-08-14T20:54:41.4335403Z log.error( 2025-08-14T20:54:41.4336875Z f"Invalid experiment name: {experiment_name}. Experiment names should only contain alphanumeric characters, '_', and '-'. They cannot contain spaces, and the special characters '_' and '-' cannot be the first or last characters." 2025-08-14T20:54:41.4338504Z ) 2025-08-14T20:54:41.4338856Z return False 2025-08-14T20:54:41.4339088Z 2025-08-14T20:54:41.4339094Z 2025-08-14T20:54:41.4339400Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-08-14T20:54:41.4340012Z """ 2025-08-14T20:54:41.4340743Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-08-14T20:54:41.4341599Z """ 2025-08-14T20:54:41.4341961Z try: 2025-08-14T20:54:41.4342317Z if settings_text: 2025-08-14T20:54:41.4343046Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-08-14T20:54:41.4343833Z # for easy reading 2025-08-14T20:54:41.4344608Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-08-14T20:54:41.4345498Z # the backtick character in shell commands. 2025-08-14T20:54:41.4346091Z backtick = chr(96) # backtick character 2025-08-14T20:54:41.4346753Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-08-14T20:54:41.4347417Z settings = load_yaml(settings_text) 2025-08-14T20:54:41.4347800Z 2025-08-14T20:54:41.4348200Z # For now we just load experiments. We can expand this if/when we add more settings 2025-08-14T20:54:41.4348973Z experiments = {} 2025-08-14T20:54:41.4349270Z 2025-08-14T20:54:41.4349649Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-08-14T20:54:41.4350417Z if not is_valid_experiment_name(exp_name): 2025-08-14T20:54:41.4351631Z # Exclude invalid experiments from the list. We log an error, but don't raise an exception so that other experiments can still be processed. 2025-08-14T20:54:41.4352682Z continue 2025-08-14T20:54:41.4352964Z 2025-08-14T20:54:41.4353143Z valid_settings = {} 2025-08-14T20:54:41.4353660Z for setting in exp_settings: 2025-08-14T20:54:41.4354227Z if setting not in Experiment._fields: 2025-08-14T20:54:41.4354776Z log.warning( 2025-08-14T20:54:41.4355477Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-08-14T20:54:41.4356329Z ) 2025-08-14T20:54:41.4356749Z else: 2025-08-14T20:54:41.4357257Z valid_settings[setting] = exp_settings[setting] 2025-08-14T20:54:41.4357686Z 2025-08-14T20:54:41.4357956Z experiments[exp_name] = Experiment(**valid_settings) 2025-08-14T20:54:41.4358582Z return Settings(experiments) 2025-08-14T20:54:41.4358929Z 2025-08-14T20:54:41.4359099Z except Exception: 2025-08-14T20:54:41.4359573Z log.exception("Failed to parse settings") 2025-08-14T20:54:41.4359955Z 2025-08-14T20:54:41.4360121Z return Settings() 2025-08-14T20:54:41.4360377Z 2025-08-14T20:54:41.4360383Z 2025-08-14T20:54:41.4360622Z def parse_settings(rollout_state: str) -> Settings: 2025-08-14T20:54:41.4361329Z """ 2025-08-14T20:54:41.4361789Z Parse settings, if any, from the rollout state. 2025-08-14T20:54:41.4362193Z 2025-08-14T20:54:41.4362540Z If the issue body contains "---" then the text above that is the settings 2025-08-14T20:54:41.4363312Z and the text below is the list of opted in users. 2025-08-14T20:54:41.4363722Z 2025-08-14T20:54:41.4364130Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-08-14T20:54:41.4364850Z """ 2025-08-14T20:54:41.4365397Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:54:41.4366148Z return parse_settings_from_text(settings_text) 2025-08-14T20:54:41.4366550Z 2025-08-14T20:54:41.4366557Z 2025-08-14T20:54:41.4366795Z def parse_users(rollout_state: str) -> UserOptins: 2025-08-14T20:54:41.4367349Z """ 2025-08-14T20:54:41.4367769Z Parse users from the rollout state. 2025-08-14T20:54:41.4368129Z 2025-08-14T20:54:41.4368280Z """ 2025-08-14T20:54:41.4368791Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-08-14T20:54:41.4369545Z return parse_user_opt_in_from_text(users_text) 2025-08-14T20:54:41.4369942Z 2025-08-14T20:54:41.4369954Z 2025-08-14T20:54:41.4370497Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:54:41.4371356Z """ 2025-08-14T20:54:41.4371771Z Check if a user is opted into an experiment 2025-08-14T20:54:41.4372301Z """ 2025-08-14T20:54:41.4372742Z return experiment_name in user_optins.get(user, []) 2025-08-14T20:54:41.4373164Z 2025-08-14T20:54:41.4373171Z 2025-08-14T20:54:41.4373589Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-08-14T20:54:41.4374331Z """ 2025-08-14T20:54:41.4374774Z Check if a user explicitly opted out of an experiment 2025-08-14T20:54:41.4375337Z """ 2025-08-14T20:54:41.4375828Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-08-14T20:54:41.4376503Z experiment_optout = "-" + experiment_name 2025-08-14T20:54:41.4377138Z if experiment_optout not in user_optins.get(user, []): 2025-08-14T20:54:41.4377730Z return False 2025-08-14T20:54:41.4377992Z 2025-08-14T20:54:41.4378260Z if is_user_opted_in(user, user_optins, experiment_name): 2025-08-14T20:54:41.4378850Z log.warning( 2025-08-14T20:54:41.4379645Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-08-14T20:54:41.4380543Z ) 2025-08-14T20:54:41.4380746Z 2025-08-14T20:54:41.4380906Z return True 2025-08-14T20:54:41.4381231Z 2025-08-14T20:54:41.4381243Z 2025-08-14T20:54:41.4381419Z def get_runner_prefix( 2025-08-14T20:54:41.4381845Z rollout_state: str, 2025-08-14T20:54:41.4382306Z workflow_requestors: Iterable[str], 2025-08-14T20:54:41.4382808Z branch: str, 2025-08-14T20:54:41.4383283Z eligible_experiments: frozenset[str] = frozenset(), 2025-08-14T20:54:41.4383943Z opt_out_experiments: frozenset[str] = frozenset(), 2025-08-14T20:54:41.4384520Z is_canary: bool = False, 2025-08-14T20:54:41.4384969Z ) -> str: 2025-08-14T20:54:41.4385375Z settings = parse_settings(rollout_state) 2025-08-14T20:54:41.4386137Z user_optins = parse_users(rollout_state) 2025-08-14T20:54:41.4386503Z 2025-08-14T20:54:41.4386670Z fleet_prefix = "" 2025-08-14T20:54:41.4387081Z prefixes = [] 2025-08-14T20:54:41.4387710Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-08-14T20:54:41.4388660Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-08-14T20:54:41.4389378Z log.info( 2025-08-14T20:54:41.4390039Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-08-14T20:54:41.4390797Z ) 2025-08-14T20:54:41.4391262Z continue 2025-08-14T20:54:41.4391515Z 2025-08-14T20:54:41.4391701Z if opt_out_experiments: 2025-08-14T20:54:41.4392239Z if experiment_name in opt_out_experiments: 2025-08-14T20:54:41.4392879Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-08-14T20:54:41.4393475Z log.info( 2025-08-14T20:54:41.4394402Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-08-14T20:54:41.4395390Z ) 2025-08-14T20:54:41.4395774Z continue 2025-08-14T20:54:41.4396039Z 2025-08-14T20:54:41.4396222Z if eligible_experiments: 2025-08-14T20:54:41.4396796Z if experiment_name not in eligible_experiments: 2025-08-14T20:54:41.4397435Z exp_list = ", ".join(eligible_experiments) 2025-08-14T20:54:41.4397992Z log.info( 2025-08-14T20:54:41.4398766Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-08-14T20:54:41.4399609Z ) 2025-08-14T20:54:41.4399993Z continue 2025-08-14T20:54:41.4400452Z elif not experiment_settings.default: 2025-08-14T20:54:41.4400977Z log.info( 2025-08-14T20:54:41.4401915Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-08-14T20:54:41.4402664Z ) 2025-08-14T20:54:41.4403034Z continue 2025-08-14T20:54:41.4403276Z 2025-08-14T20:54:41.4403555Z # Is any workflow_requestor opted out to this experiment? 2025-08-14T20:54:41.4404169Z opted_out_users = [ 2025-08-14T20:54:41.4404610Z requestor 2025-08-14T20:54:41.4405054Z for requestor in workflow_requestors 2025-08-14T20:54:41.4405724Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-08-14T20:54:41.4406346Z ] 2025-08-14T20:54:41.4406553Z 2025-08-14T20:54:41.4406724Z if opted_out_users: 2025-08-14T20:54:41.4407160Z log.info( 2025-08-14T20:54:41.4407778Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-08-14T20:54:41.4408477Z ) 2025-08-14T20:54:41.4408841Z continue 2025-08-14T20:54:41.4409090Z 2025-08-14T20:54:41.4409373Z # Is any workflow_requestor opted in to this experiment? 2025-08-14T20:54:41.4409998Z opted_in_users = [ 2025-08-14T20:54:41.4410441Z requestor 2025-08-14T20:54:41.4410884Z for requestor in workflow_requestors 2025-08-14T20:54:41.4412101Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-08-14T20:54:41.4412716Z ] 2025-08-14T20:54:41.4412919Z 2025-08-14T20:54:41.4413083Z enabled = False 2025-08-14T20:54:41.4413511Z if opted_in_users: 2025-08-14T20:54:41.4413937Z log.info( 2025-08-14T20:54:41.4414531Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-08-14T20:54:41.4415212Z ) 2025-08-14T20:54:41.4415594Z enabled = True 2025-08-14T20:54:41.4415869Z 2025-08-14T20:54:41.4416077Z elif experiment_settings.rollout_perc: 2025-08-14T20:54:41.4416926Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-08-14T20:54:41.4418018Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-08-14T20:54:41.4418673Z log.info( 2025-08-14T20:54:41.4419540Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-08-14T20:54:41.4420470Z ) 2025-08-14T20:54:41.4420870Z enabled = True 2025-08-14T20:54:41.4421270Z 2025-08-14T20:54:41.4421434Z if enabled: 2025-08-14T20:54:41.4421855Z label = experiment_name 2025-08-14T20:54:41.4422397Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-08-14T20:54:41.4423237Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-08-14T20:54:41.4424136Z # - If it's enabled, then we always list it's prefix first 2025-08-14T20:54:41.4424891Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-08-14T20:54:41.4425560Z if is_canary: 2025-08-14T20:54:41.4426041Z label += CANARY_FLEET_SUFFIX 2025-08-14T20:54:41.4426592Z fleet_prefix = label 2025-08-14T20:54:41.4427075Z else: 2025-08-14T20:54:41.4427498Z prefixes.append(label) 2025-08-14T20:54:41.4427853Z 2025-08-14T20:54:41.4428036Z if len(prefixes) > 1: 2025-08-14T20:54:41.4428465Z log.error( 2025-08-14T20:54:41.4429512Z f"Only a fleet and one other experiment can be enabled for a job at any time. Enabling {prefixes[0]} and ignoring the rest, which are {', '.join(prefixes[1:])}" 2025-08-14T20:54:41.4430635Z ) 2025-08-14T20:54:41.4431019Z prefixes = prefixes[:1] 2025-08-14T20:54:41.4431427Z 2025-08-14T20:54:41.4431608Z # Fleet always comes first 2025-08-14T20:54:41.4432071Z if fleet_prefix: 2025-08-14T20:54:41.4432503Z prefixes.insert(0, fleet_prefix) 2025-08-14T20:54:41.4432872Z 2025-08-14T20:54:41.4433234Z return ".".join(prefixes) + "." if prefixes else "" 2025-08-14T20:54:41.4433653Z 2025-08-14T20:54:41.4433661Z 2025-08-14T20:54:41.4434112Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-08-14T20:54:41.4434890Z """ 2025-08-14T20:54:41.4435476Z Gets the first comment of the issue, which contains the desired rollout state. 2025-08-14T20:54:41.4436039Z 2025-08-14T20:54:41.4436425Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-08-14T20:54:41.4437132Z """ 2025-08-14T20:54:41.4437518Z gh = get_gh_client(github_token) 2025-08-14T20:54:41.4438043Z issue = get_issue(gh, repo, issue_num) 2025-08-14T20:54:41.4438668Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-08-14T20:54:41.4439116Z 2025-08-14T20:54:41.4439123Z 2025-08-14T20:54:41.4439512Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-08-14T20:54:41.4440276Z for _ in range(num_retries): 2025-08-14T20:54:41.4440745Z try: 2025-08-14T20:54:41.4441257Z req = Request(url=url, headers=headers) 2025-08-14T20:54:41.4441929Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-08-14T20:54:41.4442567Z return json.loads(content) 2025-08-14T20:54:41.4443092Z except Exception as e: 2025-08-14T20:54:41.4443620Z log.warning(f"Could not download {url}: {e}") 2025-08-14T20:54:41.4444029Z 2025-08-14T20:54:41.4444402Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-08-14T20:54:41.4445102Z return {} 2025-08-14T20:54:41.4445329Z 2025-08-14T20:54:41.4445336Z 2025-08-14T20:54:41.4445489Z @cache 2025-08-14T20:54:41.4446104Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-08-14T20:54:41.4446850Z """ 2025-08-14T20:54:41.4447237Z Dynamically get PR information 2025-08-14T20:54:41.4447709Z """ 2025-08-14T20:54:41.4448337Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-08-14T20:54:41.4448955Z headers = { 2025-08-14T20:54:41.4449400Z "Accept": "application/vnd.github.v3+json", 2025-08-14T20:54:41.4450003Z "Authorization": f"token {github_token}", 2025-08-14T20:54:41.4450534Z } 2025-08-14T20:54:41.4450949Z json_response: dict[str, Any] = download_json( 2025-08-14T20:54:41.4451648Z url=f"{github_api}/issues/{pr_number}", 2025-08-14T20:54:41.4452192Z headers=headers, 2025-08-14T20:54:41.4452608Z ) 2025-08-14T20:54:41.4452799Z 2025-08-14T20:54:41.4452982Z if not json_response: 2025-08-14T20:54:41.4453541Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-08-14T20:54:41.4454153Z return {} 2025-08-14T20:54:41.4454387Z 2025-08-14T20:54:41.4454560Z return json_response 2025-08-14T20:54:41.4454842Z 2025-08-14T20:54:41.4454849Z 2025-08-14T20:54:41.4455250Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-08-14T20:54:41.4455984Z """ 2025-08-14T20:54:41.4456499Z Dynamically get the latest list of labels from the pull request 2025-08-14T20:54:41.4457156Z """ 2025-08-14T20:54:41.4457622Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-08-14T20:54:41.4458234Z return { 2025-08-14T20:54:41.4458821Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-08-14T20:54:41.4459530Z } 2025-08-14T20:54:41.4459730Z 2025-08-14T20:54:41.4459737Z 2025-08-14T20:54:41.4459906Z def main() -> None: 2025-08-14T20:54:41.4460312Z args = parse_args() 2025-08-14T20:54:41.4460575Z 2025-08-14T20:54:41.4460793Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-08-14T20:54:41.4461271Z 2025-08-14T20:54:41.4461459Z # Check if the PR is opt-out 2025-08-14T20:54:41.4461942Z if args.pr_number: 2025-08-14T20:54:41.4462591Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-08-14T20:54:41.4463353Z if OPT_OUT_LABEL in labels: 2025-08-14T20:54:41.4463956Z log.info( 2025-08-14T20:54:41.4464663Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-08-14T20:54:41.4465428Z ) 2025-08-14T20:54:41.4465963Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:54:41.4466632Z sys.exit() 2025-08-14T20:54:41.4466887Z 2025-08-14T20:54:41.4467043Z try: 2025-08-14T20:54:41.4467503Z rollout_state = get_rollout_state_from_issue( 2025-08-14T20:54:41.4468208Z args.github_token, args.github_issue_repo, args.github_issue 2025-08-14T20:54:41.4468847Z ) 2025-08-14T20:54:41.4469046Z 2025-08-14T20:54:41.4469253Z username = get_potential_pr_author( 2025-08-14T20:54:41.4469784Z args.github_token, 2025-08-14T20:54:41.4470255Z args.github_repo, 2025-08-14T20:54:41.4470715Z args.github_actor, 2025-08-14T20:54:41.4471291Z args.github_ref_type, 2025-08-14T20:54:41.4471791Z args.github_branch, 2025-08-14T20:54:41.4472247Z ) 2025-08-14T20:54:41.4472444Z 2025-08-14T20:54:41.4472722Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-08-14T20:54:41.4473177Z 2025-08-14T20:54:41.4473385Z runner_label_prefix = get_runner_prefix( 2025-08-14T20:54:41.4473941Z rollout_state, 2025-08-14T20:54:41.4474411Z (args.github_issue_owner, username), 2025-08-14T20:54:41.4474956Z args.github_branch, 2025-08-14T20:54:41.4475443Z args.eligible_experiments, 2025-08-14T20:54:41.4475974Z args.opt_out_experiments, 2025-08-14T20:54:41.4476470Z is_canary, 2025-08-14T20:54:41.4476874Z ) 2025-08-14T20:54:41.4477072Z 2025-08-14T20:54:41.4477249Z except Exception as e: 2025-08-14T20:54:41.4477697Z log.error( 2025-08-14T20:54:41.4478365Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-08-14T20:54:41.4479258Z ) 2025-08-14T20:54:41.4479460Z 2025-08-14T20:54:41.4479789Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-08-14T20:54:41.4480290Z 2025-08-14T20:54:41.4480297Z 2025-08-14T20:54:41.4480472Z if __name__ == "__main__": 2025-08-14T20:54:41.4480904Z main() 2025-08-14T20:54:41.4481110Z 2025-08-14T20:54:41.4574379Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:54:41.4575268Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-08-14T20:54:41.4615907Z shell: /usr/bin/bash -e {0} 2025-08-14T20:54:41.4616381Z env: 2025-08-14T20:54:41.4617006Z GITHUB_TOKEN: *** 2025-08-14T20:54:41.4617410Z ISSUE_NUMBER: 5132 2025-08-14T20:54:41.4617839Z TRIGGERING_ACTOR: huydhn 2025-08-14T20:54:41.4618291Z ISSUE_OWNER: 2025-08-14T20:54:41.4618689Z CHECK_EXPERIMENTS: 2025-08-14T20:54:41.4619108Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:54:41.4619533Z PR_NUMBER: 2025-08-14T20:54:41.4619931Z ##[endgroup] 2025-08-14T20:54:41.8779520Z Defaulting to user installation because normal site-packages is not writeable 2025-08-14T20:54:42.3403350Z Collecting urllib3==1.26.18 2025-08-14T20:54:42.3908506Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-08-14T20:54:42.4125716Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.7 MB/s eta 0:00:00 2025-08-14T20:54:42.4432365Z Collecting PyGithub==2.3.0 2025-08-14T20:54:42.4525979Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-08-14T20:54:42.5011603Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-08-14T20:54:42.5085172Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl.metadata (8.6 kB) 2025-08-14T20:54:42.5147441Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-08-14T20:54:42.5166074Z Requirement already satisfied: pyjwt>=2.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (2.7.0) 2025-08-14T20:54:42.5180907Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-08-14T20:54:42.5512212Z Collecting Deprecated (from PyGithub==2.3.0) 2025-08-14T20:54:42.5589370Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-08-14T20:54:42.5828535Z Requirement already satisfied: cryptography>=3.4.0 in /usr/lib/python3/dist-packages (from pyjwt[crypto]>=2.4.0->PyGithub==2.3.0) (41.0.7) 2025-08-14T20:54:42.7078302Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:54:42.7152005Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-08-14T20:54:42.8365522Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-08-14T20:54:42.8443385Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.4 kB) 2025-08-14T20:54:42.8709701Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-08-14T20:54:42.8783835Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-08-14T20:54:42.9057944Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-08-14T20:54:42.9200581Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 10.9 MB/s eta 0:00:00 2025-08-14T20:54:42.9272256Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-08-14T20:54:42.9529057Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 14.4 MB/s eta 0:00:00 2025-08-14T20:54:42.9610506Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-08-14T20:54:43.0087160Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 18.5 MB/s eta 0:00:00 2025-08-14T20:54:43.0158799Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-08-14T20:54:43.0250222Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-08-14T20:54:43.0464630Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 23.8 MB/s eta 0:00:00 2025-08-14T20:54:43.0533274Z Downloading wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (88 kB) 2025-08-14T20:54:43.0585259Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88.0/88.0 kB 21.7 MB/s eta 0:00:00 2025-08-14T20:54:43.0655033Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-08-14T20:54:43.0719379Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 21.9 MB/s eta 0:00:00 2025-08-14T20:54:43.3625629Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-08-14T20:54:43.9004998Z Successfully installed Deprecated-1.2.18 PyGithub-2.3.0 cffi-1.17.1 pycparser-2.22 pynacl-1.5.0 urllib3-1.26.18 wrapt-1.17.3 2025-08-14T20:54:43.9869911Z ##[group]Run curr_branch="main" 2025-08-14T20:54:43.9870240Z curr_branch="main" 2025-08-14T20:54:43.9870470Z curr_ref_type="branch" 2025-08-14T20:54:43.9870749Z echo "Current branch is '$curr_branch'" 2025-08-14T20:54:43.9871006Z  2025-08-14T20:54:43.9871460Z python3 runner_determinator.py \ 2025-08-14T20:54:43.9871750Z  --github-token "$GITHUB_TOKEN" \ 2025-08-14T20:54:43.9872027Z  --github-issue "$ISSUE_NUMBER" \ 2025-08-14T20:54:43.9872279Z  --github-branch "$curr_branch" \ 2025-08-14T20:54:43.9872544Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-08-14T20:54:43.9872819Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-08-14T20:54:43.9873095Z  --github-ref-type "$curr_ref_type" \ 2025-08-14T20:54:43.9873356Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-08-14T20:54:43.9873652Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-08-14T20:54:43.9874013Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-08-14T20:54:43.9874298Z  --pr-number "${PR_NUMBER}" 2025-08-14T20:54:43.9917274Z shell: /usr/bin/bash -e {0} 2025-08-14T20:54:43.9917540Z env: 2025-08-14T20:54:43.9918264Z GITHUB_TOKEN: *** 2025-08-14T20:54:43.9918461Z ISSUE_NUMBER: 5132 2025-08-14T20:54:43.9918659Z TRIGGERING_ACTOR: huydhn 2025-08-14T20:54:43.9918854Z ISSUE_OWNER: 2025-08-14T20:54:43.9919038Z CHECK_EXPERIMENTS: 2025-08-14T20:54:43.9919226Z OPT_OUT_EXPERIMENTS: 2025-08-14T20:54:43.9919412Z PR_NUMBER: 2025-08-14T20:54:43.9919575Z ##[endgroup] 2025-08-14T20:54:43.9982759Z Current branch is 'main' 2025-08-14T20:54:45.5483411Z INFO : Based on rollout percentage of 50%, enabling experiment lf. 2025-08-14T20:54:45.5484588Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-08-14T20:54:45.5485502Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-08-14T20:54:45.5486193Z INFO : Setting output: label-type='lf.' 2025-08-14T20:54:45.5795275Z Evaluate and set job outputs 2025-08-14T20:54:45.5801587Z Set output 'label-type' 2025-08-14T20:54:45.5803392Z Cleaning up orphan processes