2025-05-06T20:10:08.7159163Z Current runner version: '2.323.0' 2025-05-06T20:10:08.7183630Z ##[group]Operating System 2025-05-06T20:10:08.7184469Z Ubuntu 2025-05-06T20:10:08.7184921Z 24.04.2 2025-05-06T20:10:08.7185382Z LTS 2025-05-06T20:10:08.7185853Z ##[endgroup] 2025-05-06T20:10:08.7186386Z ##[group]Runner Image 2025-05-06T20:10:08.7187203Z Image: ubuntu-24.04 2025-05-06T20:10:08.7187818Z Version: 20250427.1.0 2025-05-06T20:10:08.7188843Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250427.1/images/ubuntu/Ubuntu2404-Readme.md 2025-05-06T20:10:08.7190174Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250427.1 2025-05-06T20:10:08.7191093Z ##[endgroup] 2025-05-06T20:10:08.7191585Z ##[group]Runner Image Provisioner 2025-05-06T20:10:08.7192190Z 2.0.422.1 2025-05-06T20:10:08.7192597Z ##[endgroup] 2025-05-06T20:10:08.7193639Z ##[group]GITHUB_TOKEN Permissions 2025-05-06T20:10:08.7195419Z Contents: read 2025-05-06T20:10:08.7196047Z Metadata: read 2025-05-06T20:10:08.7196712Z ##[endgroup] 2025-05-06T20:10:08.7199181Z Secret source: Actions 2025-05-06T20:10:08.7199861Z Prepare workflow directory 2025-05-06T20:10:08.7713094Z Prepare all required actions 2025-05-06T20:10:08.7766125Z Complete job name: before-test / get-label-type / runner-determinator 2025-05-06T20:10:09.3399574Z ##[group]Run cat < runner_determinator.py 2025-05-06T20:10:09.3401894Z cat < runner_determinator.py 2025-05-06T20:10:09.3402874Z # flake8: noqa: G004 2025-05-06T20:10:09.3403423Z  2025-05-06T20:10:09.3404220Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-05-06T20:10:09.3405355Z # must be kept in sync. You can do it easily by running the following command: 2025-05-06T20:10:09.3406341Z # python .github/scripts/update_runner_determinator.py 2025-05-06T20:10:09.3407359Z  2025-05-06T20:10:09.3407899Z """ 2025-05-06T20:10:09.3408659Z This runner determinator is used to determine which set of runners to run a 2025-05-06T20:10:09.3409914Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-05-06T20:10:09.3411186Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-05-06T20:10:09.3412167Z of which runners should be used to run which job. 2025-05-06T20:10:09.3412981Z  2025-05-06T20:10:09.3413712Z The configuration has two parts, the settings and a list of opted-in users, 2025-05-06T20:10:09.3414802Z separated by a line containing "---". If the line is not present, the 2025-05-06T20:10:09.3415896Z settings are considered to be empty with only the second part, the user 2025-05-06T20:10:09.3416722Z list, defined. 2025-05-06T20:10:09.3417513Z  2025-05-06T20:10:09.3418185Z The first part is a YAML block that defines the rollout settings. This can be 2025-05-06T20:10:09.3419361Z used to define any settings that are needed to determine which runners to use. 2025-05-06T20:10:09.3420385Z It's fields are defined by the RolloutSettings class below. 2025-05-06T20:10:09.3421078Z  2025-05-06T20:10:09.3421898Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-05-06T20:10:09.3422908Z The user list is also a comma separated list of additional features or 2025-05-06T20:10:09.3423799Z experiments which the user could be opted in to. 2025-05-06T20:10:09.3424574Z  2025-05-06T20:10:09.3425062Z The user list has the following rules: 2025-05-06T20:10:09.3425693Z  2025-05-06T20:10:09.3426393Z - Users are GitHub usernames, which must start with the @ prefix 2025-05-06T20:10:09.3427687Z - Each user is also a comma-separated list of features/experiments to enable 2025-05-06T20:10:09.3428587Z - A "#" prefix opts the user out of all experiments 2025-05-06T20:10:09.3429593Z  2025-05-06T20:10:09.3430198Z Example config: 2025-05-06T20:10:09.3430809Z  # A list of experiments that can be opted into. 2025-05-06T20:10:09.3431746Z  # This defines the behavior they'll induce when opted into. 2025-05-06T20:10:09.3432525Z  # Expected syntax is: 2025-05-06T20:10:09.3433319Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-05-06T20:10:09.3434503Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-05-06T20:10:09.3435366Z  2025-05-06T20:10:09.3435854Z  experiments: 2025-05-06T20:10:09.3436431Z  lf: 2025-05-06T20:10:09.3437114Z  rollout_percent: 25 2025-05-06T20:10:09.3437774Z  all_branches: false 2025-05-06T20:10:09.3438397Z  default: true 2025-05-06T20:10:09.3438988Z  --- 2025-05-06T20:10:09.3439457Z  2025-05-06T20:10:09.3440003Z  # Opt-ins: 2025-05-06T20:10:09.3440752Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-05-06T20:10:09.3441948Z  # and specifying experiments to enable in a comma-separated list. 2025-05-06T20:10:09.3442944Z  # To always opt out of an experiment, prefix it with a "-". 2025-05-06T20:10:09.3443753Z  # Experiments should be from the above list. 2025-05-06T20:10:09.3444417Z  2025-05-06T20:10:09.3444890Z  @User1,-lf,split_build 2025-05-06T20:10:09.3445560Z  @User2,lf 2025-05-06T20:10:09.3446075Z  @User3,split_build 2025-05-06T20:10:09.3446629Z """ 2025-05-06T20:10:09.3447491Z  2025-05-06T20:10:09.3447961Z import json 2025-05-06T20:10:09.3448525Z import logging 2025-05-06T20:10:09.3449112Z import os 2025-05-06T20:10:09.3449628Z import random 2025-05-06T20:10:09.3450147Z import re 2025-05-06T20:10:09.3450730Z import sys 2025-05-06T20:10:09.3451286Z from argparse import ArgumentParser 2025-05-06T20:10:09.3452030Z from collections.abc import Iterable 2025-05-06T20:10:09.3452766Z from functools import cache 2025-05-06T20:10:09.3453386Z from logging import LogRecord 2025-05-06T20:10:09.3454055Z from typing import Any, NamedTuple 2025-05-06T20:10:09.3531591Z from urllib.request import Request, urlopen 2025-05-06T20:10:09.3532310Z  2025-05-06T20:10:09.3532707Z import yaml 2025-05-06T20:10:09.3533186Z from github import Auth, Github 2025-05-06T20:10:09.3533755Z from github.Issue import Issue 2025-05-06T20:10:09.3534270Z  2025-05-06T20:10:09.3534658Z  2025-05-06T20:10:09.3535139Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-05-06T20:10:09.3535914Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-05-06T20:10:09.3537117Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-05-06T20:10:09.3538085Z  2025-05-06T20:10:09.3538581Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-05-06T20:10:09.3539219Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-05-06T20:10:09.3539818Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-05-06T20:10:09.3540469Z OPT_OUT_LABEL = "no-runner-experiments" 2025-05-06T20:10:09.3541055Z  2025-05-06T20:10:09.3541497Z SETTING_EXPERIMENTS = "experiments" 2025-05-06T20:10:09.3542041Z  2025-05-06T20:10:09.3542447Z LF_FLEET_EXPERIMENT = "lf" 2025-05-06T20:10:09.3542977Z CANARY_FLEET_SUFFIX = ".c" 2025-05-06T20:10:09.3543481Z  2025-05-06T20:10:09.3543846Z  2025-05-06T20:10:09.3544263Z class Experiment(NamedTuple): 2025-05-06T20:10:09.3544815Z  rollout_perc: float = ( 2025-05-06T20:10:09.3545786Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-05-06T20:10:09.3546523Z  ) 2025-05-06T20:10:09.3547175Z  all_branches: bool = ( 2025-05-06T20:10:09.3547915Z  False # If True, the experiment is also enabled on the exception branches 2025-05-06T20:10:09.3548693Z  ) 2025-05-06T20:10:09.3549114Z  default: bool = ( 2025-05-06T20:10:09.3549798Z  True # If True, the experiment is enabled by default for all queries 2025-05-06T20:10:09.3550500Z  ) 2025-05-06T20:10:09.3550896Z  2025-05-06T20:10:09.3551298Z  # Add more fields as needed 2025-05-06T20:10:09.3551818Z  2025-05-06T20:10:09.3552183Z  2025-05-06T20:10:09.3552587Z class Settings(NamedTuple): 2025-05-06T20:10:09.3553104Z  """ 2025-05-06T20:10:09.3553643Z  Settings for the experiments that can be opted into. 2025-05-06T20:10:09.3554294Z  """ 2025-05-06T20:10:09.3554686Z  2025-05-06T20:10:09.3555127Z  experiments: dict[str, Experiment] = {} 2025-05-06T20:10:09.3555678Z  2025-05-06T20:10:09.3556187Z  2025-05-06T20:10:09.3556641Z class ColorFormatter(logging.Formatter): 2025-05-06T20:10:09.3557478Z  """Color codes the log messages based on the log level""" 2025-05-06T20:10:09.3558127Z  2025-05-06T20:10:09.3558509Z  COLORS = { 2025-05-06T20:10:09.3559002Z  "WARNING": "\033[33m", # Yellow 2025-05-06T20:10:09.3559569Z  "ERROR": "\033[31m", # Red 2025-05-06T20:10:09.3560130Z  "CRITICAL": "\033[31m", # Red 2025-05-06T20:10:09.3560692Z  "INFO": "\033[0m", # Reset 2025-05-06T20:10:09.3561251Z  "DEBUG": "\033[0m", # Reset 2025-05-06T20:10:09.3561777Z  } 2025-05-06T20:10:09.3562170Z  2025-05-06T20:10:09.3562637Z  def format(self, record: LogRecord) -> str: 2025-05-06T20:10:09.3563463Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-05-06T20:10:09.3564307Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-05-06T20:10:09.3564939Z  return super().format(record) 2025-05-06T20:10:09.3565472Z  2025-05-06T20:10:09.3565842Z  2025-05-06T20:10:09.3566271Z handler = logging.StreamHandler() 2025-05-06T20:10:09.3567175Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-05-06T20:10:09.3567947Z  2025-05-06T20:10:09.3568449Z log = logging.getLogger(os.path.basename(__file__)) 2025-05-06T20:10:09.3569109Z log.addHandler(handler) 2025-05-06T20:10:09.3569638Z log.setLevel(logging.INFO) 2025-05-06T20:10:09.3570126Z  2025-05-06T20:10:09.3570497Z  2025-05-06T20:10:09.3571001Z def set_github_output(key: str, value: str) -> None: 2025-05-06T20:10:09.3571617Z  """ 2025-05-06T20:10:09.3572198Z  Defines outputs of the github action that invokes this script 2025-05-06T20:10:09.3572870Z  """ 2025-05-06T20:10:09.3573291Z  if not GITHUB_OUTPUT: 2025-05-06T20:10:09.3574440Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-05-06T20:10:09.3575628Z  log.warning( 2025-05-06T20:10:09.3576585Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-05-06T20:10:09.3577857Z  ) 2025-05-06T20:10:09.3578366Z  print(f"::set-output name={key}::{value}") 2025-05-06T20:10:09.3578949Z  return 2025-05-06T20:10:09.3579380Z  2025-05-06T20:10:09.3579961Z  with open(GITHUB_OUTPUT, "a") as f: 2025-05-06T20:10:09.3580593Z  log.info(f"Setting output: {key}='{value}'") 2025-05-06T20:10:09.3581228Z  f.write(f"{key}={value}\n") 2025-05-06T20:10:09.3581751Z  2025-05-06T20:10:09.3582125Z  2025-05-06T20:10:09.3582679Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-05-06T20:10:09.3583391Z  return frozenset( 2025-05-06T20:10:09.3584095Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-05-06T20:10:09.3584823Z  ) 2025-05-06T20:10:09.3585214Z  2025-05-06T20:10:09.3585585Z  2025-05-06T20:10:09.3585989Z def parse_args() -> Any: 2025-05-06T20:10:09.3586648Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-05-06T20:10:09.3587723Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-05-06T20:10:09.3588545Z  parser.add_argument( 2025-05-06T20:10:09.3589079Z  "--github-issue-repo", 2025-05-06T20:10:09.3589612Z  type=str, 2025-05-06T20:10:09.3590091Z  required=False, 2025-05-06T20:10:09.3590753Z  default="pytorch/test-infra", 2025-05-06T20:10:09.3591374Z  help="GitHub repo to get the issue", 2025-05-06T20:10:09.3591929Z  ) 2025-05-06T20:10:09.3592339Z  parser.add_argument( 2025-05-06T20:10:09.3592851Z  "--github-repo", 2025-05-06T20:10:09.3593349Z  type=str, 2025-05-06T20:10:09.3593825Z  required=True, 2025-05-06T20:10:09.3594370Z  help="GitHub repo where CI is running", 2025-05-06T20:10:09.3594932Z  ) 2025-05-06T20:10:09.3595361Z  parser.add_argument( 2025-05-06T20:10:09.3596052Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-05-06T20:10:09.3596765Z  ) 2025-05-06T20:10:09.3597293Z  parser.add_argument( 2025-05-06T20:10:09.3598011Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-05-06T20:10:09.3598728Z  ) 2025-05-06T20:10:09.3599149Z  parser.add_argument( 2025-05-06T20:10:09.3599869Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-05-06T20:10:09.3600607Z  ) 2025-05-06T20:10:09.3601035Z  parser.add_argument( 2025-05-06T20:10:09.3601783Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-05-06T20:10:09.3602536Z  ) 2025-05-06T20:10:09.3602968Z  parser.add_argument( 2025-05-06T20:10:09.3603495Z  "--github-ref-type", 2025-05-06T20:10:09.3604013Z  type=str, 2025-05-06T20:10:09.3604479Z  required=True, 2025-05-06T20:10:09.3605062Z  help="Current GitHub ref type, branch or tag", 2025-05-06T20:10:09.3605645Z  ) 2025-05-06T20:10:09.3606061Z  parser.add_argument( 2025-05-06T20:10:09.3606596Z  "--eligible-experiments", 2025-05-06T20:10:09.3607292Z  type=_str_comma_separated_to_set, 2025-05-06T20:10:09.3607862Z  required=False, 2025-05-06T20:10:09.3608351Z  default="", 2025-05-06T20:10:09.3609306Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-05-06T20:10:09.3610271Z  ) 2025-05-06T20:10:09.3610687Z  parser.add_argument( 2025-05-06T20:10:09.3611194Z  "--pr-number", 2025-05-06T20:10:09.3611685Z  type=str, 2025-05-06T20:10:09.3612156Z  required=False, 2025-05-06T20:10:09.3612647Z  default="", 2025-05-06T20:10:09.3613347Z  help="the optional PR number where this is run", 2025-05-06T20:10:09.3613939Z  ) 2025-05-06T20:10:09.3614323Z  2025-05-06T20:10:09.3614723Z  return parser.parse_args() 2025-05-06T20:10:09.3615246Z  2025-05-06T20:10:09.3615607Z  2025-05-06T20:10:09.3616254Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-05-06T20:10:09.3617353Z  auth = Auth.Token(github_token) 2025-05-06T20:10:09.3617942Z  return Github(auth=auth) 2025-05-06T20:10:09.3618448Z  2025-05-06T20:10:09.3618818Z  2025-05-06T20:10:09.3619530Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-05-06T20:10:09.3620386Z  repo = gh.get_repo(repo) 2025-05-06T20:10:09.3620969Z  return repo.get_issue(number=issue_num) 2025-05-06T20:10:09.3621517Z  2025-05-06T20:10:09.3621878Z  2025-05-06T20:10:09.3622284Z def get_potential_pr_author( 2025-05-06T20:10:09.3623014Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-05-06T20:10:09.3623741Z ) -> str: 2025-05-06T20:10:09.3624472Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-05-06T20:10:09.3625377Z  # Fetch the actual username from the original PR. The PR number is 2025-05-06T20:10:09.3626194Z  # embedded in the tag name: ciflow// 2025-05-06T20:10:09.3626807Z  2025-05-06T20:10:09.3627846Z  gh = get_gh_client(github_token) 2025-05-06T20:10:09.3628402Z  2025-05-06T20:10:09.3628932Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-05-06T20:10:09.3629631Z  split_tag = ref_name.split("/") 2025-05-06T20:10:09.3630190Z  if ( 2025-05-06T20:10:09.3630650Z  len(split_tag) == 3 2025-05-06T20:10:09.3631222Z  and split_tag[0] == "ciflow" 2025-05-06T20:10:09.3631809Z  and split_tag[2].isnumeric() 2025-05-06T20:10:09.3632348Z  ): 2025-05-06T20:10:09.3632808Z  pr_number = split_tag[2] 2025-05-06T20:10:09.3633354Z  try: 2025-05-06T20:10:09.3633864Z  repository = gh.get_repo(repo) 2025-05-06T20:10:09.3634545Z  pull = repository.get_pull(number=int(pr_number)) 2025-05-06T20:10:09.3635212Z  except Exception as e: 2025-05-06T20:10:09.3635805Z  raise Exception( # noqa: TRY002 2025-05-06T20:10:09.3636541Z  f"issue with pull request {pr_number} from repo {repository}" 2025-05-06T20:10:09.3637364Z  ) from e 2025-05-06T20:10:09.3637994Z  return pull.user.login # type: ignore[no-any-return] 2025-05-06T20:10:09.3638770Z  # In all other cases, return the original input username 2025-05-06T20:10:09.3639419Z  return username 2025-05-06T20:10:09.3639869Z  2025-05-06T20:10:09.3640229Z  2025-05-06T20:10:09.3640696Z def is_exception_branch(branch: str) -> bool: 2025-05-06T20:10:09.3641261Z  """ 2025-05-06T20:10:09.3641979Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-05-06T20:10:09.3642790Z  """ 2025-05-06T20:10:09.3643403Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-05-06T20:10:09.3644106Z  2025-05-06T20:10:09.3644476Z  2025-05-06T20:10:09.3644893Z def load_yaml(yaml_text: str) -> Any: 2025-05-06T20:10:09.3645435Z  try: 2025-05-06T20:10:09.3645889Z  data = yaml.safe_load(yaml_text) 2025-05-06T20:10:09.3646441Z  return data 2025-05-06T20:10:09.3647187Z  except yaml.YAMLError: 2025-05-06T20:10:09.3647748Z  log.exception("Error loading YAML") 2025-05-06T20:10:09.3648302Z  raise 2025-05-06T20:10:09.3648717Z  2025-05-06T20:10:09.3649086Z  2025-05-06T20:10:09.3649751Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-05-06T20:10:09.3650533Z  """ 2025-05-06T20:10:09.3651227Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-05-06T20:10:09.3652007Z  2025-05-06T20:10:09.3652586Z  If the issue body contains "---" then the text above that is the settings 2025-05-06T20:10:09.3653399Z  and the text below is the list of opted in users. 2025-05-06T20:10:09.3653993Z  2025-05-06T20:10:09.3654603Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-05-06T20:10:09.3655348Z  """ 2025-05-06T20:10:09.3655864Z  rollout_state_parts = rollout_state.split("---") 2025-05-06T20:10:09.3656530Z  if len(rollout_state_parts) >= 2: 2025-05-06T20:10:09.3657645Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-05-06T20:10:09.3658306Z  else: 2025-05-06T20:10:09.3658745Z  return "", rollout_state 2025-05-06T20:10:09.3659263Z  2025-05-06T20:10:09.3659617Z  2025-05-06T20:10:09.3660048Z class UserOptins(dict[str, list[str]]): 2025-05-06T20:10:09.3660600Z  """ 2025-05-06T20:10:09.3661206Z  Dictionary of users with a list of features they have opted into 2025-05-06T20:10:09.3661896Z  """ 2025-05-06T20:10:09.3662293Z  2025-05-06T20:10:09.3662646Z  2025-05-06T20:10:09.3663402Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-05-06T20:10:09.3664187Z  """ 2025-05-06T20:10:09.3664979Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-05-06T20:10:09.3665871Z  2025-05-06T20:10:09.3666731Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-05-06T20:10:09.3667916Z  - Example line: "@User1,lf,split_build" 2025-05-06T20:10:09.3668663Z  - A "#" prefix indicates the user is opted out of all experiments 2025-05-06T20:10:09.3669343Z  2025-05-06T20:10:09.3669709Z  2025-05-06T20:10:09.3670065Z  """ 2025-05-06T20:10:09.3670484Z  optins = UserOptins() 2025-05-06T20:10:09.3671048Z  for user in user_optin_text.split("\n"): 2025-05-06T20:10:09.3671660Z  user = user.strip("\r\n\t -") 2025-05-06T20:10:09.3672263Z  if not user or not user.startswith("@"): 2025-05-06T20:10:09.3672885Z  # Not a valid user. Skip 2025-05-06T20:10:09.3673427Z  continue 2025-05-06T20:10:09.3673870Z  2025-05-06T20:10:09.3674238Z  if user: 2025-05-06T20:10:09.3674746Z  usr_name = user.split(",")[0].strip("@") 2025-05-06T20:10:09.3675513Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-05-06T20:10:09.3676184Z  2025-05-06T20:10:09.3676564Z  return optins 2025-05-06T20:10:09.3677108Z  2025-05-06T20:10:09.3677463Z  2025-05-06T20:10:09.3678005Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-05-06T20:10:09.3678657Z  """ 2025-05-06T20:10:09.3679128Z  Check if the experiment name is valid. 2025-05-06T20:10:09.3679729Z  A valid name: 2025-05-06T20:10:09.3680456Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-05-06T20:10:09.3681590Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-05-06T20:10:09.3682355Z  - Cannot contain spaces 2025-05-06T20:10:09.3682862Z  """ 2025-05-06T20:10:09.3683241Z  2025-05-06T20:10:09.3683739Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-05-06T20:10:09.3684500Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-05-06T20:10:09.3685125Z  2025-05-06T20:10:09.3685486Z  if valid: 2025-05-06T20:10:09.3685917Z  return True 2025-05-06T20:10:09.3686357Z  2025-05-06T20:10:09.3686727Z  log.error( 2025-05-06T20:10:09.3688400Z  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-05-06T20:10:09.3689985Z  ) 2025-05-06T20:10:09.3690379Z  return False 2025-05-06T20:10:09.3690807Z  2025-05-06T20:10:09.3691159Z  2025-05-06T20:10:09.3691832Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-05-06T20:10:09.3692675Z  """ 2025-05-06T20:10:09.3693414Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-05-06T20:10:09.3694167Z  """ 2025-05-06T20:10:09.3694550Z  try: 2025-05-06T20:10:09.3694955Z  if settings_text: 2025-05-06T20:10:09.3695759Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-05-06T20:10:09.3696591Z  # for easy reading 2025-05-06T20:10:09.3697763Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-05-06T20:10:09.3698735Z  # the backtick character in shell commands. 2025-05-06T20:10:09.3699411Z  backtick = chr(96) # backtick character 2025-05-06T20:10:09.3700136Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-05-06T20:10:09.3700856Z  settings = load_yaml(settings_text) 2025-05-06T20:10:09.3701398Z  2025-05-06T20:10:09.3702029Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-05-06T20:10:09.3702810Z  experiments = {} 2025-05-06T20:10:09.3703299Z  2025-05-06T20:10:09.3703888Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-05-06T20:10:09.3704740Z  if not is_valid_experiment_name(exp_name): 2025-05-06T20:10:09.3705905Z  # 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-05-06T20:10:09.3707139Z  continue 2025-05-06T20:10:09.3707623Z  2025-05-06T20:10:09.3708022Z  valid_settings = {} 2025-05-06T20:10:09.3708606Z  for setting in exp_settings: 2025-05-06T20:10:09.3709235Z  if setting not in Experiment._fields: 2025-05-06T20:10:09.3709841Z  log.warning( 2025-05-06T20:10:09.3710614Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-05-06T20:10:09.3711369Z  ) 2025-05-06T20:10:09.3711856Z  else: 2025-05-06T20:10:09.3712443Z  valid_settings[setting] = exp_settings[setting] 2025-05-06T20:10:09.3713051Z  2025-05-06T20:10:09.3713561Z  experiments[exp_name] = Experiment(**valid_settings) 2025-05-06T20:10:09.3714403Z  return Settings(experiments) 2025-05-06T20:10:09.3714939Z  2025-05-06T20:10:09.3715317Z  except Exception: 2025-05-06T20:10:09.3715892Z  log.exception("Failed to parse settings") 2025-05-06T20:10:09.3716467Z  2025-05-06T20:10:09.3716952Z  return Settings() 2025-05-06T20:10:09.3717416Z  2025-05-06T20:10:09.3717766Z  2025-05-06T20:10:09.3718254Z def parse_settings(rollout_state: str) -> Settings: 2025-05-06T20:10:09.3718865Z  """ 2025-05-06T20:10:09.3719357Z  Parse settings, if any, from the rollout state. 2025-05-06T20:10:09.3719940Z  2025-05-06T20:10:09.3720513Z  If the issue body contains "---" then the text above that is the settings 2025-05-06T20:10:09.3721330Z  and the text below is the list of opted in users. 2025-05-06T20:10:09.3721915Z  2025-05-06T20:10:09.3722555Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-05-06T20:10:09.3723327Z  """ 2025-05-06T20:10:09.3723954Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:10:09.3724902Z  return parse_settings_from_text(settings_text) 2025-05-06T20:10:09.3725497Z  2025-05-06T20:10:09.3725849Z  2025-05-06T20:10:09.3726340Z def parse_users(rollout_state: str) -> UserOptins: 2025-05-06T20:10:09.3727042Z  """ 2025-05-06T20:10:09.3727489Z  Parse users from the rollout state. 2025-05-06T20:10:09.3728021Z  2025-05-06T20:10:09.3728368Z  """ 2025-05-06T20:10:09.3728966Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:10:09.3729760Z  return parse_user_opt_in_from_text(users_text) 2025-05-06T20:10:09.3730337Z  2025-05-06T20:10:09.3730680Z  2025-05-06T20:10:09.3731352Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:10:09.3732144Z  """ 2025-05-06T20:10:09.3732616Z  Check if a user is opted into an experiment 2025-05-06T20:10:09.3733198Z  """ 2025-05-06T20:10:09.3733712Z  return experiment_name in user_optins.get(user, []) 2025-05-06T20:10:09.3734328Z  2025-05-06T20:10:09.3734684Z  2025-05-06T20:10:09.3735348Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:10:09.3736147Z  """ 2025-05-06T20:10:09.3736668Z  Check if a user explicitly opted out of an experiment 2025-05-06T20:10:09.3737416Z  """ 2025-05-06T20:10:09.3737975Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-05-06T20:10:09.3738725Z  experiment_optout = "-" + experiment_name 2025-05-06T20:10:09.3739434Z  if experiment_optout not in user_optins.get(user, []): 2025-05-06T20:10:09.3740078Z  return False 2025-05-06T20:10:09.3740521Z  2025-05-06T20:10:09.3741028Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-05-06T20:10:09.3741665Z  log.warning( 2025-05-06T20:10:09.3742533Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-05-06T20:10:09.3743441Z  ) 2025-05-06T20:10:09.3743832Z  2025-05-06T20:10:09.3744200Z  return True 2025-05-06T20:10:09.3744626Z  2025-05-06T20:10:09.3744983Z  2025-05-06T20:10:09.3745368Z def get_runner_prefix( 2025-05-06T20:10:09.3745864Z  rollout_state: str, 2025-05-06T20:10:09.3746394Z  workflow_requestors: Iterable[str], 2025-05-06T20:10:09.3747041Z  branch: str, 2025-05-06T20:10:09.3747790Z  eligible_experiments: frozenset[str] = frozenset(), 2025-05-06T20:10:09.3748435Z  is_canary: bool = False, 2025-05-06T20:10:09.3748936Z ) -> str: 2025-05-06T20:10:09.3749417Z  settings = parse_settings(rollout_state) 2025-05-06T20:10:09.3750053Z  user_optins = parse_users(rollout_state) 2025-05-06T20:10:09.3750605Z  2025-05-06T20:10:09.3750982Z  fleet_prefix = "" 2025-05-06T20:10:09.3751465Z  prefixes = [] 2025-05-06T20:10:09.3752166Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-05-06T20:10:09.3753164Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-05-06T20:10:09.3753911Z  log.info( 2025-05-06T20:10:09.3754666Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-05-06T20:10:09.3755454Z  ) 2025-05-06T20:10:09.3755881Z  continue 2025-05-06T20:10:09.3756322Z  2025-05-06T20:10:09.3756726Z  if eligible_experiments: 2025-05-06T20:10:09.3757602Z  if experiment_name not in eligible_experiments: 2025-05-06T20:10:09.3758295Z  exp_list = ", ".join(eligible_experiments) 2025-05-06T20:10:09.3758895Z  log.info( 2025-05-06T20:10:09.3759756Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-05-06T20:10:09.3760614Z  ) 2025-05-06T20:10:09.3761069Z  continue 2025-05-06T20:10:09.3761609Z  elif not experiment_settings.default: 2025-05-06T20:10:09.3762177Z  log.info( 2025-05-06T20:10:09.3762908Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-05-06T20:10:09.3763677Z  ) 2025-05-06T20:10:09.3764113Z  continue 2025-05-06T20:10:09.3764552Z  2025-05-06T20:10:09.3765054Z  # Is any workflow_requestor opted out to this experiment? 2025-05-06T20:10:09.3765728Z  opted_out_users = [ 2025-05-06T20:10:09.3766248Z  requestor 2025-05-06T20:10:09.3766779Z  for requestor in workflow_requestors 2025-05-06T20:10:09.3767628Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-05-06T20:10:09.3768295Z  ] 2025-05-06T20:10:09.3768686Z  2025-05-06T20:10:09.3769076Z  if opted_out_users: 2025-05-06T20:10:09.3769577Z  log.info( 2025-05-06T20:10:09.3770280Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-05-06T20:10:09.3771004Z  ) 2025-05-06T20:10:09.3771435Z  continue 2025-05-06T20:10:09.3771887Z  2025-05-06T20:10:09.3772382Z  # Is any workflow_requestor opted in to this experiment? 2025-05-06T20:10:09.3773031Z  opted_in_users = [ 2025-05-06T20:10:09.3773533Z  requestor 2025-05-06T20:10:09.3774056Z  for requestor in workflow_requestors 2025-05-06T20:10:09.3774773Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-05-06T20:10:09.3775427Z  ] 2025-05-06T20:10:09.3775815Z  2025-05-06T20:10:09.3776188Z  enabled = False 2025-05-06T20:10:09.3776682Z  if opted_in_users: 2025-05-06T20:10:09.3777282Z  log.info( 2025-05-06T20:10:09.3777961Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-05-06T20:10:09.3778668Z  ) 2025-05-06T20:10:09.3779108Z  enabled = True 2025-05-06T20:10:09.3779726Z  2025-05-06T20:10:09.3780171Z  elif experiment_settings.rollout_perc: 2025-05-06T20:10:09.3781083Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-05-06T20:10:09.3782082Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-05-06T20:10:09.3782772Z  log.info( 2025-05-06T20:10:09.3783702Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-05-06T20:10:09.3784658Z  ) 2025-05-06T20:10:09.3785126Z  enabled = True 2025-05-06T20:10:09.3785623Z  2025-05-06T20:10:09.3785996Z  if enabled: 2025-05-06T20:10:09.3786485Z  label = experiment_name 2025-05-06T20:10:09.3787235Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-05-06T20:10:09.3788121Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-05-06T20:10:09.3789182Z  # - If it's enabled, then we always list it's prefix first 2025-05-06T20:10:09.3790022Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-05-06T20:10:09.3790730Z  if is_canary: 2025-05-06T20:10:09.3791281Z  label += CANARY_FLEET_SUFFIX 2025-05-06T20:10:09.3791864Z  fleet_prefix = label 2025-05-06T20:10:09.3792411Z  else: 2025-05-06T20:10:09.3792906Z  prefixes.append(label) 2025-05-06T20:10:09.3793443Z  2025-05-06T20:10:09.3793834Z  if len(prefixes) > 1: 2025-05-06T20:10:09.3794330Z  log.error( 2025-05-06T20:10:09.3795451Z  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-05-06T20:10:09.3796599Z  ) 2025-05-06T20:10:09.3797154Z  prefixes = prefixes[:1] 2025-05-06T20:10:09.3797668Z  2025-05-06T20:10:09.3798063Z  # Fleet always comes first 2025-05-06T20:10:09.3798593Z  if fleet_prefix: 2025-05-06T20:10:09.3799107Z  prefixes.insert(0, fleet_prefix) 2025-05-06T20:10:09.3799649Z  2025-05-06T20:10:09.3800126Z  return ".".join(prefixes) + "." if prefixes else "" 2025-05-06T20:10:09.3800725Z  2025-05-06T20:10:09.3801076Z  2025-05-06T20:10:09.3801748Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-05-06T20:10:09.3802553Z  """ 2025-05-06T20:10:09.3803190Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-05-06T20:10:09.3803932Z  2025-05-06T20:10:09.3804549Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-05-06T20:10:09.3805288Z  """ 2025-05-06T20:10:09.3805727Z  gh = get_gh_client(github_token) 2025-05-06T20:10:09.3806333Z  issue = get_issue(gh, repo, issue_num) 2025-05-06T20:10:09.3807135Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-05-06T20:10:09.3807768Z  2025-05-06T20:10:09.3808129Z  2025-05-06T20:10:09.3808751Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-05-06T20:10:09.3809560Z  for _ in range(num_retries): 2025-05-06T20:10:09.3810073Z  try: 2025-05-06T20:10:09.3810558Z  req = Request(url=url, headers=headers) 2025-05-06T20:10:09.3811282Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-05-06T20:10:09.3812104Z  return json.loads(content) 2025-05-06T20:10:09.3812674Z  except Exception as e: 2025-05-06T20:10:09.3813278Z  log.warning(f"Could not download {url}: {e}") 2025-05-06T20:10:09.3813875Z  2025-05-06T20:10:09.3814481Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-05-06T20:10:09.3815237Z  return {} 2025-05-06T20:10:09.3815944Z  2025-05-06T20:10:09.3816311Z  2025-05-06T20:10:09.3816672Z @cache 2025-05-06T20:10:09.3817469Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-05-06T20:10:09.3818274Z  """ 2025-05-06T20:10:09.3818713Z  Dynamically get PR information 2025-05-06T20:10:09.3819324Z  """ 2025-05-06T20:10:09.3819876Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-05-06T20:10:09.3820542Z  headers = { 2025-05-06T20:10:09.3821088Z  "Accept": "application/vnd.github.v3+json", 2025-05-06T20:10:09.3821742Z  "Authorization": f"token {github_token}", 2025-05-06T20:10:09.3822305Z  } 2025-05-06T20:10:09.3822923Z  json_response: dict[str, Any] = download_json( 2025-05-06T20:10:09.3823747Z  url=f"{github_api}/issues/{pr_number}", 2025-05-06T20:10:09.3824335Z  headers=headers, 2025-05-06T20:10:09.3824813Z  ) 2025-05-06T20:10:09.3825191Z  2025-05-06T20:10:09.3825573Z  if not json_response: 2025-05-06T20:10:09.3826253Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-05-06T20:10:09.3827278Z  return {} 2025-05-06T20:10:09.3827720Z  2025-05-06T20:10:09.3828104Z  return json_response 2025-05-06T20:10:09.3828578Z  2025-05-06T20:10:09.3828927Z  2025-05-06T20:10:09.3829567Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-05-06T20:10:09.3830312Z  """ 2025-05-06T20:10:09.3830897Z  Dynamically get the latest list of labels from the pull request 2025-05-06T20:10:09.3831589Z  """ 2025-05-06T20:10:09.3832118Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-05-06T20:10:09.3832760Z  return { 2025-05-06T20:10:09.3833397Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-05-06T20:10:09.3834121Z  } 2025-05-06T20:10:09.3834497Z  2025-05-06T20:10:09.3834855Z  2025-05-06T20:10:09.3835233Z def main() -> None: 2025-05-06T20:10:09.3835705Z  args = parse_args() 2025-05-06T20:10:09.3836187Z  2025-05-06T20:10:09.3836632Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-05-06T20:10:09.3837327Z  2025-05-06T20:10:09.3837736Z  # Check if the PR is opt-out 2025-05-06T20:10:09.3838280Z  if args.pr_number: 2025-05-06T20:10:09.3839002Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-05-06T20:10:09.3839820Z  if OPT_OUT_LABEL in labels: 2025-05-06T20:10:09.3840366Z  log.info( 2025-05-06T20:10:09.3841117Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-05-06T20:10:09.3841911Z  ) 2025-05-06T20:10:09.3842531Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:10:09.3843243Z  sys.exit() 2025-05-06T20:10:09.3843698Z  2025-05-06T20:10:09.3844055Z  try: 2025-05-06T20:10:09.3844540Z  rollout_state = get_rollout_state_from_issue( 2025-05-06T20:10:09.3845298Z  args.github_token, args.github_issue_repo, args.github_issue 2025-05-06T20:10:09.3846130Z  ) 2025-05-06T20:10:09.3846516Z  2025-05-06T20:10:09.3847041Z  username = get_potential_pr_author( 2025-05-06T20:10:09.3847625Z  args.github_token, 2025-05-06T20:10:09.3848159Z  args.github_repo, 2025-05-06T20:10:09.3848688Z  args.github_actor, 2025-05-06T20:10:09.3849227Z  args.github_ref_type, 2025-05-06T20:10:09.3849785Z  args.github_branch, 2025-05-06T20:10:09.3850287Z  ) 2025-05-06T20:10:09.3850683Z  2025-05-06T20:10:09.3851190Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-05-06T20:10:09.3851823Z  2025-05-06T20:10:09.3852267Z  runner_label_prefix = get_runner_prefix( 2025-05-06T20:10:09.3852850Z  rollout_state, 2025-05-06T20:10:09.3853400Z  (args.github_issue_owner, username), 2025-05-06T20:10:09.3853978Z  args.github_branch, 2025-05-06T20:10:09.3854528Z  args.eligible_experiments, 2025-05-06T20:10:09.3855070Z  is_canary, 2025-05-06T20:10:09.3855654Z  ) 2025-05-06T20:10:09.3856052Z  2025-05-06T20:10:09.3856438Z  except Exception as e: 2025-05-06T20:10:09.3857036Z  log.error( 2025-05-06T20:10:09.3857782Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-05-06T20:10:09.3858569Z  ) 2025-05-06T20:10:09.3858968Z  2025-05-06T20:10:09.3859532Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:10:09.3860218Z  2025-05-06T20:10:09.3860575Z  2025-05-06T20:10:09.3860958Z if __name__ == "__main__": 2025-05-06T20:10:09.3861453Z  main() 2025-05-06T20:10:09.3861852Z  2025-05-06T20:10:09.3862224Z EOF 2025-05-06T20:10:09.3862594Z  2025-05-06T20:10:09.3862987Z cat runner_determinator.py 2025-05-06T20:10:09.4074675Z shell: /usr/bin/bash -e {0} 2025-05-06T20:10:09.4075632Z env: 2025-05-06T20:10:09.4076313Z GITHUB_TOKEN: *** 2025-05-06T20:10:09.4076740Z ISSUE_NUMBER: 5132 2025-05-06T20:10:09.4077366Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:10:09.4077883Z ISSUE_OWNER: 2025-05-06T20:10:09.4078292Z CHECK_EXPERIMENTS: 2025-05-06T20:10:09.4078716Z PR_NUMBER: 2025-05-06T20:10:09.4079107Z ##[endgroup] 2025-05-06T20:10:09.4278930Z # flake8: noqa: G004 2025-05-06T20:10:09.4279275Z 2025-05-06T20:10:09.4279726Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-05-06T20:10:09.4280679Z # must be kept in sync. You can do it easily by running the following command: 2025-05-06T20:10:09.4281485Z # python .github/scripts/update_runner_determinator.py 2025-05-06T20:10:09.4281931Z 2025-05-06T20:10:09.4282119Z """ 2025-05-06T20:10:09.4282707Z This runner determinator is used to determine which set of runners to run a 2025-05-06T20:10:09.4283596Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-05-06T20:10:09.4284535Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-05-06T20:10:09.4285352Z of which runners should be used to run which job. 2025-05-06T20:10:09.4285816Z 2025-05-06T20:10:09.4286208Z The configuration has two parts, the settings and a list of opted-in users, 2025-05-06T20:10:09.4287361Z separated by a line containing "---". If the line is not present, the 2025-05-06T20:10:09.4288269Z settings are considered to be empty with only the second part, the user 2025-05-06T20:10:09.4288974Z list, defined. 2025-05-06T20:10:09.4289208Z 2025-05-06T20:10:09.4289579Z The first part is a YAML block that defines the rollout settings. This can be 2025-05-06T20:10:09.4290526Z used to define any settings that are needed to determine which runners to use. 2025-05-06T20:10:09.4291583Z It's fields are defined by the RolloutSettings class below. 2025-05-06T20:10:09.4292047Z 2025-05-06T20:10:09.4292452Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-05-06T20:10:09.4293361Z The user list is also a comma separated list of additional features or 2025-05-06T20:10:09.4294104Z experiments which the user could be opted in to. 2025-05-06T20:10:09.4294520Z 2025-05-06T20:10:09.4294727Z The user list has the following rules: 2025-05-06T20:10:09.4295074Z 2025-05-06T20:10:09.4295387Z - Users are GitHub usernames, which must start with the @ prefix 2025-05-06T20:10:09.4296254Z - Each user is also a comma-separated list of features/experiments to enable 2025-05-06T20:10:09.4297205Z - A "#" prefix opts the user out of all experiments 2025-05-06T20:10:09.4297621Z 2025-05-06T20:10:09.4297789Z Example config: 2025-05-06T20:10:09.4298248Z # A list of experiments that can be opted into. 2025-05-06T20:10:09.4298919Z # This defines the behavior they'll induce when opted into. 2025-05-06T20:10:09.4299564Z # Expected syntax is: 2025-05-06T20:10:09.4300215Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-05-06T20:10:09.4301337Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-05-06T20:10:09.4301956Z 2025-05-06T20:10:09.4302132Z experiments: 2025-05-06T20:10:09.4302516Z lf: 2025-05-06T20:10:09.4302900Z rollout_percent: 25 2025-05-06T20:10:09.4303355Z all_branches: false 2025-05-06T20:10:09.4303803Z default: true 2025-05-06T20:10:09.4304208Z --- 2025-05-06T20:10:09.4304415Z 2025-05-06T20:10:09.4304575Z # Opt-ins: 2025-05-06T20:10:09.4305153Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-05-06T20:10:09.4306022Z # and specifying experiments to enable in a comma-separated list. 2025-05-06T20:10:09.4307020Z # To always opt out of an experiment, prefix it with a "-". 2025-05-06T20:10:09.4307720Z # Experiments should be from the above list. 2025-05-06T20:10:09.4308103Z 2025-05-06T20:10:09.4308294Z @User1,-lf,split_build 2025-05-06T20:10:09.4308734Z @User2,lf 2025-05-06T20:10:09.4309130Z @User3,split_build 2025-05-06T20:10:09.4309545Z """ 2025-05-06T20:10:09.4309745Z 2025-05-06T20:10:09.4309909Z import json 2025-05-06T20:10:09.4310279Z import logging 2025-05-06T20:10:09.4310660Z import os 2025-05-06T20:10:09.4311011Z import random 2025-05-06T20:10:09.4311393Z import re 2025-05-06T20:10:09.4311759Z import sys 2025-05-06T20:10:09.4312156Z from argparse import ArgumentParser 2025-05-06T20:10:09.4312689Z from collections.abc import Iterable 2025-05-06T20:10:09.4313206Z from functools import cache 2025-05-06T20:10:09.4313689Z from logging import LogRecord 2025-05-06T20:10:09.4314178Z from typing import Any, NamedTuple 2025-05-06T20:10:09.4314723Z from urllib.request import Request, urlopen 2025-05-06T20:10:09.4315103Z 2025-05-06T20:10:09.4315274Z import yaml 2025-05-06T20:10:09.4315671Z from github import Auth, Github 2025-05-06T20:10:09.4316165Z from github.Issue import Issue 2025-05-06T20:10:09.4316478Z 2025-05-06T20:10:09.4316484Z 2025-05-06T20:10:09.4316711Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-05-06T20:10:09.4317812Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-05-06T20:10:09.4318691Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-05-06T20:10:09.4319256Z 2025-05-06T20:10:09.4319495Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-05-06T20:10:09.4320078Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-05-06T20:10:09.4320596Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-05-06T20:10:09.4321155Z OPT_OUT_LABEL = "no-runner-experiments" 2025-05-06T20:10:09.4321510Z 2025-05-06T20:10:09.4321710Z SETTING_EXPERIMENTS = "experiments" 2025-05-06T20:10:09.4322047Z 2025-05-06T20:10:09.4322239Z LF_FLEET_EXPERIMENT = "lf" 2025-05-06T20:10:09.4322701Z CANARY_FLEET_SUFFIX = ".c" 2025-05-06T20:10:09.4323146Z 2025-05-06T20:10:09.4323153Z 2025-05-06T20:10:09.4323343Z class Experiment(NamedTuple): 2025-05-06T20:10:09.4323817Z rollout_perc: float = ( 2025-05-06T20:10:09.4324469Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-05-06T20:10:09.4325144Z ) 2025-05-06T20:10:09.4325513Z all_branches: bool = ( 2025-05-06T20:10:09.4326140Z False # If True, the experiment is also enabled on the exception branches 2025-05-06T20:10:09.4326814Z ) 2025-05-06T20:10:09.4327418Z default: bool = ( 2025-05-06T20:10:09.4328011Z True # If True, the experiment is enabled by default for all queries 2025-05-06T20:10:09.4328656Z ) 2025-05-06T20:10:09.4328856Z 2025-05-06T20:10:09.4329041Z # Add more fields as needed 2025-05-06T20:10:09.4329355Z 2025-05-06T20:10:09.4329361Z 2025-05-06T20:10:09.4329549Z class Settings(NamedTuple): 2025-05-06T20:10:09.4330003Z """ 2025-05-06T20:10:09.4330458Z Settings for the experiments that can be opted into. 2025-05-06T20:10:09.4331061Z """ 2025-05-06T20:10:09.4331259Z 2025-05-06T20:10:09.4331466Z experiments: dict[str, Experiment] = {} 2025-05-06T20:10:09.4331835Z 2025-05-06T20:10:09.4331841Z 2025-05-06T20:10:09.4332184Z class ColorFormatter(logging.Formatter): 2025-05-06T20:10:09.4332836Z """Color codes the log messages based on the log level""" 2025-05-06T20:10:09.4333283Z 2025-05-06T20:10:09.4333446Z COLORS = { 2025-05-06T20:10:09.4333858Z "WARNING": "\033[33m", # Yellow 2025-05-06T20:10:09.4334380Z "ERROR": "\033[31m", # Red 2025-05-06T20:10:09.4334882Z "CRITICAL": "\033[31m", # Red 2025-05-06T20:10:09.4335386Z "INFO": "\033[0m", # Reset 2025-05-06T20:10:09.4335876Z "DEBUG": "\033[0m", # Reset 2025-05-06T20:10:09.4336349Z } 2025-05-06T20:10:09.4336553Z 2025-05-06T20:10:09.4336772Z def format(self, record: LogRecord) -> str: 2025-05-06T20:10:09.4337844Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-05-06T20:10:09.4338649Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-05-06T20:10:09.4339245Z return super().format(record) 2025-05-06T20:10:09.4339587Z 2025-05-06T20:10:09.4339594Z 2025-05-06T20:10:09.4339798Z handler = logging.StreamHandler() 2025-05-06T20:10:09.4340512Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-05-06T20:10:09.4341067Z 2025-05-06T20:10:09.4341320Z log = logging.getLogger(os.path.basename(__file__)) 2025-05-06T20:10:09.4341907Z log.addHandler(handler) 2025-05-06T20:10:09.4342349Z log.setLevel(logging.INFO) 2025-05-06T20:10:09.4342642Z 2025-05-06T20:10:09.4342649Z 2025-05-06T20:10:09.4342901Z def set_github_output(key: str, value: str) -> None: 2025-05-06T20:10:09.4343477Z """ 2025-05-06T20:10:09.4343984Z Defines outputs of the github action that invokes this script 2025-05-06T20:10:09.4344613Z """ 2025-05-06T20:10:09.4344986Z if not GITHUB_OUTPUT: 2025-05-06T20:10:09.4346059Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-05-06T20:10:09.4347381Z log.warning( 2025-05-06T20:10:09.4348250Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-05-06T20:10:09.4349178Z ) 2025-05-06T20:10:09.4359055Z print(f"::set-output name={key}::{value}") 2025-05-06T20:10:09.4359690Z return 2025-05-06T20:10:09.4359924Z 2025-05-06T20:10:09.4360143Z with open(GITHUB_OUTPUT, "a") as f: 2025-05-06T20:10:09.4360741Z log.info(f"Setting output: {key}='{value}'") 2025-05-06T20:10:09.4361312Z f.write(f"{key}={value}\n") 2025-05-06T20:10:09.4361640Z 2025-05-06T20:10:09.4361647Z 2025-05-06T20:10:09.4361968Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-05-06T20:10:09.4362603Z return frozenset( 2025-05-06T20:10:09.4363223Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-05-06T20:10:09.4364130Z ) 2025-05-06T20:10:09.4364334Z 2025-05-06T20:10:09.4364341Z 2025-05-06T20:10:09.4364527Z def parse_args() -> Any: 2025-05-06T20:10:09.4365108Z parser = ArgumentParser("Get dynamic rollout settings") 2025-05-06T20:10:09.4365981Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-05-06T20:10:09.4366759Z parser.add_argument( 2025-05-06T20:10:09.4367466Z "--github-issue-repo", 2025-05-06T20:10:09.4367934Z type=str, 2025-05-06T20:10:09.4368337Z required=False, 2025-05-06T20:10:09.4368786Z default="pytorch/test-infra", 2025-05-06T20:10:09.4369335Z help="GitHub repo to get the issue", 2025-05-06T20:10:09.4369845Z ) 2025-05-06T20:10:09.4370232Z parser.add_argument( 2025-05-06T20:10:09.4370678Z "--github-repo", 2025-05-06T20:10:09.4371119Z type=str, 2025-05-06T20:10:09.4371521Z required=True, 2025-05-06T20:10:09.4372000Z help="GitHub repo where CI is running", 2025-05-06T20:10:09.4372526Z ) 2025-05-06T20:10:09.4372896Z parser.add_argument( 2025-05-06T20:10:09.4373512Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-05-06T20:10:09.4374313Z ) 2025-05-06T20:10:09.4374697Z parser.add_argument( 2025-05-06T20:10:09.4375321Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-05-06T20:10:09.4376001Z ) 2025-05-06T20:10:09.4376367Z parser.add_argument( 2025-05-06T20:10:09.4377212Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-05-06T20:10:09.4377942Z ) 2025-05-06T20:10:09.4378318Z parser.add_argument( 2025-05-06T20:10:09.4378987Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-05-06T20:10:09.4379701Z ) 2025-05-06T20:10:09.4380075Z parser.add_argument( 2025-05-06T20:10:09.4380522Z "--github-ref-type", 2025-05-06T20:10:09.4380994Z type=str, 2025-05-06T20:10:09.4381386Z required=True, 2025-05-06T20:10:09.4381876Z help="Current GitHub ref type, branch or tag", 2025-05-06T20:10:09.4382417Z ) 2025-05-06T20:10:09.4382790Z parser.add_argument( 2025-05-06T20:10:09.4383253Z "--eligible-experiments", 2025-05-06T20:10:09.4383773Z type=_str_comma_separated_to_set, 2025-05-06T20:10:09.4384302Z required=False, 2025-05-06T20:10:09.4384722Z default="", 2025-05-06T20:10:09.4385616Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-05-06T20:10:09.4386535Z ) 2025-05-06T20:10:09.4387106Z parser.add_argument( 2025-05-06T20:10:09.4387562Z "--pr-number", 2025-05-06T20:10:09.4387971Z type=str, 2025-05-06T20:10:09.4388379Z required=False, 2025-05-06T20:10:09.4388803Z default="", 2025-05-06T20:10:09.4389279Z help="the optional PR number where this is run", 2025-05-06T20:10:09.4389857Z ) 2025-05-06T20:10:09.4390056Z 2025-05-06T20:10:09.4390251Z return parser.parse_args() 2025-05-06T20:10:09.4390560Z 2025-05-06T20:10:09.4390567Z 2025-05-06T20:10:09.4390982Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-05-06T20:10:09.4391743Z auth = Auth.Token(github_token) 2025-05-06T20:10:09.4392249Z return Github(auth=auth) 2025-05-06T20:10:09.4392551Z 2025-05-06T20:10:09.4392557Z 2025-05-06T20:10:09.4393017Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-05-06T20:10:09.4393822Z repo = gh.get_repo(repo) 2025-05-06T20:10:09.4394323Z return repo.get_issue(number=issue_num) 2025-05-06T20:10:09.4394693Z 2025-05-06T20:10:09.4394700Z 2025-05-06T20:10:09.4394894Z def get_potential_pr_author( 2025-05-06T20:10:09.4395541Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-05-06T20:10:09.4396224Z ) -> str: 2025-05-06T20:10:09.4397107Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-05-06T20:10:09.4397952Z # Fetch the actual username from the original PR. The PR number is 2025-05-06T20:10:09.4398718Z # embedded in the tag name: ciflow// 2025-05-06T20:10:09.4399150Z 2025-05-06T20:10:09.4399338Z gh = get_gh_client(github_token) 2025-05-06T20:10:09.4399681Z 2025-05-06T20:10:09.4399955Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-05-06T20:10:09.4400585Z split_tag = ref_name.split("/") 2025-05-06T20:10:09.4401091Z if ( 2025-05-06T20:10:09.4401480Z len(split_tag) == 3 2025-05-06T20:10:09.4401963Z and split_tag[0] == "ciflow" 2025-05-06T20:10:09.4402484Z and split_tag[2].isnumeric() 2025-05-06T20:10:09.4402983Z ): 2025-05-06T20:10:09.4403378Z pr_number = split_tag[2] 2025-05-06T20:10:09.4403862Z try: 2025-05-06T20:10:09.4404301Z repository = gh.get_repo(repo) 2025-05-06T20:10:09.4404922Z pull = repository.get_pull(number=int(pr_number)) 2025-05-06T20:10:09.4405524Z except Exception as e: 2025-05-06T20:10:09.4406043Z raise Exception( # noqa: TRY002 2025-05-06T20:10:09.4406994Z f"issue with pull request {pr_number} from repo {repository}" 2025-05-06T20:10:09.4407711Z ) from e 2025-05-06T20:10:09.4408258Z return pull.user.login # type: ignore[no-any-return] 2025-05-06T20:10:09.4408966Z # In all other cases, return the original input username 2025-05-06T20:10:09.4409561Z return username 2025-05-06T20:10:09.4409808Z 2025-05-06T20:10:09.4409815Z 2025-05-06T20:10:09.4410055Z def is_exception_branch(branch: str) -> bool: 2025-05-06T20:10:09.4410580Z """ 2025-05-06T20:10:09.4411227Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-05-06T20:10:09.4412015Z """ 2025-05-06T20:10:09.4461118Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-05-06T20:10:09.4462371Z 2025-05-06T20:10:09.4462383Z 2025-05-06T20:10:09.4462830Z def load_yaml(yaml_text: str) -> Any: 2025-05-06T20:10:09.4463756Z try: 2025-05-06T20:10:09.4464533Z data = yaml.safe_load(yaml_text) 2025-05-06T20:10:09.4465581Z return data 2025-05-06T20:10:09.4466332Z except yaml.YAMLError: 2025-05-06T20:10:09.4467373Z log.exception("Error loading YAML") 2025-05-06T20:10:09.4468306Z raise 2025-05-06T20:10:09.4468684Z 2025-05-06T20:10:09.4468697Z 2025-05-06T20:10:09.4469439Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-05-06T20:10:09.4470707Z """ 2025-05-06T20:10:09.4471902Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-05-06T20:10:09.4473063Z 2025-05-06T20:10:09.4473683Z If the issue body contains "---" then the text above that is the settings 2025-05-06T20:10:09.4475036Z and the text below is the list of opted in users. 2025-05-06T20:10:09.4475855Z 2025-05-06T20:10:09.4476580Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-05-06T20:10:09.4478133Z """ 2025-05-06T20:10:09.4478919Z rollout_state_parts = rollout_state.split("---") 2025-05-06T20:10:09.4480025Z if len(rollout_state_parts) >= 2: 2025-05-06T20:10:09.4481193Z return rollout_state_parts[0], rollout_state_parts[1] 2025-05-06T20:10:09.4482261Z else: 2025-05-06T20:10:09.4482918Z return "", rollout_state 2025-05-06T20:10:09.4483466Z 2025-05-06T20:10:09.4483479Z 2025-05-06T20:10:09.4483825Z class UserOptins(dict[str, list[str]]): 2025-05-06T20:10:09.4484798Z """ 2025-05-06T20:10:09.4485843Z Dictionary of users with a list of features they have opted into 2025-05-06T20:10:09.4487186Z """ 2025-05-06T20:10:09.4487552Z 2025-05-06T20:10:09.4487565Z 2025-05-06T20:10:09.4488172Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-05-06T20:10:09.4489705Z """ 2025-05-06T20:10:09.4491076Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-05-06T20:10:09.4492397Z 2025-05-06T20:10:09.4493317Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-05-06T20:10:09.4494333Z - Example line: "@User1,lf,split_build" 2025-05-06T20:10:09.4495034Z - A "#" prefix indicates the user is opted out of all experiments 2025-05-06T20:10:09.4495519Z 2025-05-06T20:10:09.4495526Z 2025-05-06T20:10:09.4495683Z """ 2025-05-06T20:10:09.4496058Z optins = UserOptins() 2025-05-06T20:10:09.4496541Z for user in user_optin_text.split("\n"): 2025-05-06T20:10:09.4497344Z user = user.strip("\r\n\t -") 2025-05-06T20:10:09.4497900Z if not user or not user.startswith("@"): 2025-05-06T20:10:09.4498457Z # Not a valid user. Skip 2025-05-06T20:10:09.4498938Z continue 2025-05-06T20:10:09.4499190Z 2025-05-06T20:10:09.4499347Z if user: 2025-05-06T20:10:09.4499791Z usr_name = user.split(",")[0].strip("@") 2025-05-06T20:10:09.4500657Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-05-06T20:10:09.4501165Z 2025-05-06T20:10:09.4501334Z return optins 2025-05-06T20:10:09.4501572Z 2025-05-06T20:10:09.4501581Z 2025-05-06T20:10:09.4501863Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-05-06T20:10:09.4502466Z """ 2025-05-06T20:10:09.4502866Z Check if the experiment name is valid. 2025-05-06T20:10:09.4503390Z A valid name: 2025-05-06T20:10:09.4504027Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-05-06T20:10:09.4504960Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-05-06T20:10:09.4505691Z - Cannot contain spaces 2025-05-06T20:10:09.4506150Z """ 2025-05-06T20:10:09.4506350Z 2025-05-06T20:10:09.4506618Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-05-06T20:10:09.4507503Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-05-06T20:10:09.4507948Z 2025-05-06T20:10:09.4508110Z if valid: 2025-05-06T20:10:09.4508491Z return True 2025-05-06T20:10:09.4508732Z 2025-05-06T20:10:09.4508894Z log.error( 2025-05-06T20:10:09.4510323Z 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-05-06T20:10:09.4511859Z ) 2025-05-06T20:10:09.4512214Z return False 2025-05-06T20:10:09.4512445Z 2025-05-06T20:10:09.4512451Z 2025-05-06T20:10:09.4512756Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-05-06T20:10:09.4513368Z """ 2025-05-06T20:10:09.4513949Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-05-06T20:10:09.4514665Z """ 2025-05-06T20:10:09.4515014Z try: 2025-05-06T20:10:09.4515381Z if settings_text: 2025-05-06T20:10:09.4516107Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-05-06T20:10:09.4517116Z # for easy reading 2025-05-06T20:10:09.4517912Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-05-06T20:10:09.4518794Z # the backtick character in shell commands. 2025-05-06T20:10:09.4519388Z backtick = chr(96) # backtick character 2025-05-06T20:10:09.4520048Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-05-06T20:10:09.4520701Z settings = load_yaml(settings_text) 2025-05-06T20:10:09.4521075Z 2025-05-06T20:10:09.4521472Z # For now we just load experiments. We can expand this if/when we add more settings 2025-05-06T20:10:09.4522222Z experiments = {} 2025-05-06T20:10:09.4522661Z 2025-05-06T20:10:09.4523025Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-05-06T20:10:09.4523782Z if not is_valid_experiment_name(exp_name): 2025-05-06T20:10:09.4524877Z # 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-05-06T20:10:09.4525921Z continue 2025-05-06T20:10:09.4526203Z 2025-05-06T20:10:09.4526392Z valid_settings = {} 2025-05-06T20:10:09.4527099Z for setting in exp_settings: 2025-05-06T20:10:09.4527859Z if setting not in Experiment._fields: 2025-05-06T20:10:09.4528410Z log.warning( 2025-05-06T20:10:09.4529109Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-05-06T20:10:09.4529815Z ) 2025-05-06T20:10:09.4530242Z else: 2025-05-06T20:10:09.4530767Z valid_settings[setting] = exp_settings[setting] 2025-05-06T20:10:09.4531188Z 2025-05-06T20:10:09.4531459Z experiments[exp_name] = Experiment(**valid_settings) 2025-05-06T20:10:09.4532211Z return Settings(experiments) 2025-05-06T20:10:09.4532564Z 2025-05-06T20:10:09.4532744Z except Exception: 2025-05-06T20:10:09.4533228Z log.exception("Failed to parse settings") 2025-05-06T20:10:09.4533620Z 2025-05-06T20:10:09.4533790Z return Settings() 2025-05-06T20:10:09.4534050Z 2025-05-06T20:10:09.4534057Z 2025-05-06T20:10:09.4534313Z def parse_settings(rollout_state: str) -> Settings: 2025-05-06T20:10:09.4534885Z """ 2025-05-06T20:10:09.4535311Z Parse settings, if any, from the rollout state. 2025-05-06T20:10:09.4535711Z 2025-05-06T20:10:09.4536069Z If the issue body contains "---" then the text above that is the settings 2025-05-06T20:10:09.4536813Z and the text below is the list of opted in users. 2025-05-06T20:10:09.4537429Z 2025-05-06T20:10:09.4537862Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-05-06T20:10:09.4538591Z """ 2025-05-06T20:10:09.4539140Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:10:09.4539904Z return parse_settings_from_text(settings_text) 2025-05-06T20:10:09.4540312Z 2025-05-06T20:10:09.4540319Z 2025-05-06T20:10:09.4540574Z def parse_users(rollout_state: str) -> UserOptins: 2025-05-06T20:10:09.4541135Z """ 2025-05-06T20:10:09.4541524Z Parse users from the rollout state. 2025-05-06T20:10:09.4541876Z 2025-05-06T20:10:09.4542034Z """ 2025-05-06T20:10:09.4542563Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:10:09.4543305Z return parse_user_opt_in_from_text(users_text) 2025-05-06T20:10:09.4543710Z 2025-05-06T20:10:09.4543717Z 2025-05-06T20:10:09.4544138Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:10:09.4544880Z """ 2025-05-06T20:10:09.4545290Z Check if a user is opted into an experiment 2025-05-06T20:10:09.4545822Z """ 2025-05-06T20:10:09.4546262Z return experiment_name in user_optins.get(user, []) 2025-05-06T20:10:09.4546681Z 2025-05-06T20:10:09.4546687Z 2025-05-06T20:10:09.4547279Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:10:09.4548031Z """ 2025-05-06T20:10:09.4548492Z Check if a user explicitly opted out of an experiment 2025-05-06T20:10:09.4549155Z """ 2025-05-06T20:10:09.4549734Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-05-06T20:10:09.4550407Z experiment_optout = "-" + experiment_name 2025-05-06T20:10:09.4551042Z if experiment_optout not in user_optins.get(user, []): 2025-05-06T20:10:09.4551628Z return False 2025-05-06T20:10:09.4551882Z 2025-05-06T20:10:09.4552147Z if is_user_opted_in(user, user_optins, experiment_name): 2025-05-06T20:10:09.4552898Z log.warning( 2025-05-06T20:10:09.4553692Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-05-06T20:10:09.4554580Z ) 2025-05-06T20:10:09.4554794Z 2025-05-06T20:10:09.4554953Z return True 2025-05-06T20:10:09.4555188Z 2025-05-06T20:10:09.4555194Z 2025-05-06T20:10:09.4555367Z def get_runner_prefix( 2025-05-06T20:10:09.4555794Z rollout_state: str, 2025-05-06T20:10:09.4556252Z workflow_requestors: Iterable[str], 2025-05-06T20:10:09.4556767Z branch: str, 2025-05-06T20:10:09.4557473Z eligible_experiments: frozenset[str] = frozenset(), 2025-05-06T20:10:09.4558070Z is_canary: bool = False, 2025-05-06T20:10:09.4558509Z ) -> str: 2025-05-06T20:10:09.4558924Z settings = parse_settings(rollout_state) 2025-05-06T20:10:09.4559501Z user_optins = parse_users(rollout_state) 2025-05-06T20:10:09.4560048Z 2025-05-06T20:10:09.4560251Z fleet_prefix = "" 2025-05-06T20:10:09.4560694Z prefixes = [] 2025-05-06T20:10:09.4561321Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-05-06T20:10:09.4562267Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-05-06T20:10:09.4563127Z log.info( 2025-05-06T20:10:09.4563818Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-05-06T20:10:09.4564572Z ) 2025-05-06T20:10:09.4564946Z continue 2025-05-06T20:10:09.4565194Z 2025-05-06T20:10:09.4565382Z if eligible_experiments: 2025-05-06T20:10:09.4565940Z if experiment_name not in eligible_experiments: 2025-05-06T20:10:09.4566565Z exp_list = ", ".join(eligible_experiments) 2025-05-06T20:10:09.4567305Z log.info( 2025-05-06T20:10:09.4568098Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-05-06T20:10:09.4568937Z ) 2025-05-06T20:10:09.4569328Z continue 2025-05-06T20:10:09.4569800Z elif not experiment_settings.default: 2025-05-06T20:10:09.4570328Z log.info( 2025-05-06T20:10:09.4570982Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-05-06T20:10:09.4571716Z ) 2025-05-06T20:10:09.4572087Z continue 2025-05-06T20:10:09.4572341Z 2025-05-06T20:10:09.4572617Z # Is any workflow_requestor opted out to this experiment? 2025-05-06T20:10:09.4573239Z opted_out_users = [ 2025-05-06T20:10:09.4573682Z requestor 2025-05-06T20:10:09.4574135Z for requestor in workflow_requestors 2025-05-06T20:10:09.4574799Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-05-06T20:10:09.4575424Z ] 2025-05-06T20:10:09.4575628Z 2025-05-06T20:10:09.4575806Z if opted_out_users: 2025-05-06T20:10:09.4576250Z log.info( 2025-05-06T20:10:09.4577136Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-05-06T20:10:09.4577917Z ) 2025-05-06T20:10:09.4578292Z continue 2025-05-06T20:10:09.4578540Z 2025-05-06T20:10:09.4578821Z # Is any workflow_requestor opted in to this experiment? 2025-05-06T20:10:09.4579430Z opted_in_users = [ 2025-05-06T20:10:09.4579862Z requestor 2025-05-06T20:10:09.4580316Z for requestor in workflow_requestors 2025-05-06T20:10:09.4580972Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-05-06T20:10:09.4581585Z ] 2025-05-06T20:10:09.4581783Z 2025-05-06T20:10:09.4581955Z enabled = False 2025-05-06T20:10:09.4582382Z if opted_in_users: 2025-05-06T20:10:09.4582835Z log.info( 2025-05-06T20:10:09.4583435Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-05-06T20:10:09.4584115Z ) 2025-05-06T20:10:09.4584644Z enabled = True 2025-05-06T20:10:09.4584930Z 2025-05-06T20:10:09.4585141Z elif experiment_settings.rollout_perc: 2025-05-06T20:10:09.4586019Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-05-06T20:10:09.4587162Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-05-06T20:10:09.4587832Z log.info( 2025-05-06T20:10:09.4588699Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-05-06T20:10:09.4589625Z ) 2025-05-06T20:10:09.4590021Z enabled = True 2025-05-06T20:10:09.4590325Z 2025-05-06T20:10:09.4590492Z if enabled: 2025-05-06T20:10:09.4590914Z label = experiment_name 2025-05-06T20:10:09.4591457Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-05-06T20:10:09.4592271Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-05-06T20:10:09.4593150Z # - If it's enabled, then we always list it's prefix first 2025-05-06T20:10:09.4594028Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-05-06T20:10:09.4594702Z if is_canary: 2025-05-06T20:10:09.4595191Z label += CANARY_FLEET_SUFFIX 2025-05-06T20:10:09.4595731Z fleet_prefix = label 2025-05-06T20:10:09.4596208Z else: 2025-05-06T20:10:09.4596625Z prefixes.append(label) 2025-05-06T20:10:09.4597081Z 2025-05-06T20:10:09.4597265Z if len(prefixes) > 1: 2025-05-06T20:10:09.4597708Z log.error( 2025-05-06T20:10:09.4598720Z 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-05-06T20:10:09.4599819Z ) 2025-05-06T20:10:09.4600207Z prefixes = prefixes[:1] 2025-05-06T20:10:09.4600534Z 2025-05-06T20:10:09.4600714Z # Fleet always comes first 2025-05-06T20:10:09.4601178Z if fleet_prefix: 2025-05-06T20:10:09.4601623Z prefixes.insert(0, fleet_prefix) 2025-05-06T20:10:09.4601983Z 2025-05-06T20:10:09.4602254Z return ".".join(prefixes) + "." if prefixes else "" 2025-05-06T20:10:09.4602661Z 2025-05-06T20:10:09.4602669Z 2025-05-06T20:10:09.4603108Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-05-06T20:10:09.4603872Z """ 2025-05-06T20:10:09.4604457Z Gets the first comment of the issue, which contains the desired rollout state. 2025-05-06T20:10:09.4605038Z 2025-05-06T20:10:09.4605424Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-05-06T20:10:09.4606125Z """ 2025-05-06T20:10:09.4606498Z gh = get_gh_client(github_token) 2025-05-06T20:10:09.4607504Z issue = get_issue(gh, repo, issue_num) 2025-05-06T20:10:09.4608162Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-05-06T20:10:09.4608622Z 2025-05-06T20:10:09.4608634Z 2025-05-06T20:10:09.4609032Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-05-06T20:10:09.4609781Z for _ in range(num_retries): 2025-05-06T20:10:09.4610248Z try: 2025-05-06T20:10:09.4610672Z req = Request(url=url, headers=headers) 2025-05-06T20:10:09.4611329Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-05-06T20:10:09.4611970Z return json.loads(content) 2025-05-06T20:10:09.4612484Z except Exception as e: 2025-05-06T20:10:09.4613014Z log.warning(f"Could not download {url}: {e}") 2025-05-06T20:10:09.4613420Z 2025-05-06T20:10:09.4613803Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-05-06T20:10:09.4614497Z return {} 2025-05-06T20:10:09.4614713Z 2025-05-06T20:10:09.4614729Z 2025-05-06T20:10:09.4614883Z @cache 2025-05-06T20:10:09.4615492Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-05-06T20:10:09.4616408Z """ 2025-05-06T20:10:09.4616793Z Dynamically get PR information 2025-05-06T20:10:09.4617410Z """ 2025-05-06T20:10:09.4617918Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-05-06T20:10:09.4618538Z headers = { 2025-05-06T20:10:09.4618990Z "Accept": "application/vnd.github.v3+json", 2025-05-06T20:10:09.4619584Z "Authorization": f"token {github_token}", 2025-05-06T20:10:09.4620112Z } 2025-05-06T20:10:09.4620533Z json_response: dict[str, Any] = download_json( 2025-05-06T20:10:09.4621130Z url=f"{github_api}/issues/{pr_number}", 2025-05-06T20:10:09.4621668Z headers=headers, 2025-05-06T20:10:09.4622084Z ) 2025-05-06T20:10:09.4622278Z 2025-05-06T20:10:09.4622455Z if not json_response: 2025-05-06T20:10:09.4623017Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-05-06T20:10:09.4623627Z return {} 2025-05-06T20:10:09.4623869Z 2025-05-06T20:10:09.4624048Z return json_response 2025-05-06T20:10:09.4624320Z 2025-05-06T20:10:09.4624333Z 2025-05-06T20:10:09.4624732Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-05-06T20:10:09.4626200Z """ 2025-05-06T20:10:09.4626759Z Dynamically get the latest list of labels from the pull request 2025-05-06T20:10:09.4627543Z """ 2025-05-06T20:10:09.4628028Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-05-06T20:10:09.4628643Z return { 2025-05-06T20:10:09.4629392Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-05-06T20:10:09.4630316Z } 2025-05-06T20:10:09.4630522Z 2025-05-06T20:10:09.4630530Z 2025-05-06T20:10:09.4630707Z def main() -> None: 2025-05-06T20:10:09.4631120Z args = parse_args() 2025-05-06T20:10:09.4631385Z 2025-05-06T20:10:09.4631604Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-05-06T20:10:09.4631990Z 2025-05-06T20:10:09.4632188Z # Check if the PR is opt-out 2025-05-06T20:10:09.4632672Z if args.pr_number: 2025-05-06T20:10:09.4633314Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-05-06T20:10:09.4634082Z if OPT_OUT_LABEL in labels: 2025-05-06T20:10:09.4634567Z log.info( 2025-05-06T20:10:09.4635256Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-05-06T20:10:09.4636014Z ) 2025-05-06T20:10:09.4636558Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:10:09.4637335Z sys.exit() 2025-05-06T20:10:09.4637601Z 2025-05-06T20:10:09.4637757Z try: 2025-05-06T20:10:09.4638186Z rollout_state = get_rollout_state_from_issue( 2025-05-06T20:10:09.4638881Z args.github_token, args.github_issue_repo, args.github_issue 2025-05-06T20:10:09.4639508Z ) 2025-05-06T20:10:09.4639714Z 2025-05-06T20:10:09.4639914Z username = get_potential_pr_author( 2025-05-06T20:10:09.4640457Z args.github_token, 2025-05-06T20:10:09.4640926Z args.github_repo, 2025-05-06T20:10:09.4641398Z args.github_actor, 2025-05-06T20:10:09.4641875Z args.github_ref_type, 2025-05-06T20:10:09.4642375Z args.github_branch, 2025-05-06T20:10:09.4642834Z ) 2025-05-06T20:10:09.4643038Z 2025-05-06T20:10:09.4643317Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-05-06T20:10:09.4643764Z 2025-05-06T20:10:09.4643986Z runner_label_prefix = get_runner_prefix( 2025-05-06T20:10:09.4644528Z rollout_state, 2025-05-06T20:10:09.4645005Z (args.github_issue_owner, username), 2025-05-06T20:10:09.4645542Z args.github_branch, 2025-05-06T20:10:09.4646036Z args.eligible_experiments, 2025-05-06T20:10:09.4646542Z is_canary, 2025-05-06T20:10:09.4647043Z ) 2025-05-06T20:10:09.4647251Z 2025-05-06T20:10:09.4647443Z except Exception as e: 2025-05-06T20:10:09.4648071Z log.error( 2025-05-06T20:10:09.4648736Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-05-06T20:10:09.4649477Z ) 2025-05-06T20:10:09.4649682Z 2025-05-06T20:10:09.4650013Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:10:09.4650514Z 2025-05-06T20:10:09.4650522Z 2025-05-06T20:10:09.4650704Z if __name__ == "__main__": 2025-05-06T20:10:09.4651142Z main() 2025-05-06T20:10:09.4651347Z 2025-05-06T20:10:09.4735230Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-05-06T20:10:09.4736131Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-05-06T20:10:09.4765822Z shell: /usr/bin/bash -e {0} 2025-05-06T20:10:09.4766306Z env: 2025-05-06T20:10:09.4767043Z GITHUB_TOKEN: *** 2025-05-06T20:10:09.4767477Z ISSUE_NUMBER: 5132 2025-05-06T20:10:09.4767941Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:10:09.4768470Z ISSUE_OWNER: 2025-05-06T20:10:09.4768882Z CHECK_EXPERIMENTS: 2025-05-06T20:10:09.4769312Z PR_NUMBER: 2025-05-06T20:10:09.4769710Z ##[endgroup] 2025-05-06T20:10:09.8240813Z Defaulting to user installation because normal site-packages is not writeable 2025-05-06T20:10:10.1508964Z Collecting urllib3==1.26.18 2025-05-06T20:10:10.2355389Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-05-06T20:10:10.2704999Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 1.8 MB/s eta 0:00:00 2025-05-06T20:10:10.2930069Z Collecting PyGithub==2.3.0 2025-05-06T20:10:10.3133093Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-05-06T20:10:10.3553585Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-05-06T20:10:10.3757259Z 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-05-06T20:10:10.3812423Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-05-06T20:10:10.3828076Z 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-05-06T20:10:10.3842119Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-05-06T20:10:10.4139557Z Collecting Deprecated (from PyGithub==2.3.0) 2025-05-06T20:10:10.4342451Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-05-06T20:10:10.4573568Z 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-05-06T20:10:10.5661576Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-05-06T20:10:10.5866590Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-05-06T20:10:10.6902201Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-05-06T20:10:10.7107076Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.4 kB) 2025-05-06T20:10:10.7313547Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-05-06T20:10:10.7518229Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-05-06T20:10:10.7910616Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-05-06T20:10:10.8165057Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 5.8 MB/s eta 0:00:00 2025-05-06T20:10:10.8367956Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-05-06T20:10:10.8621494Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 14.6 MB/s eta 0:00:00 2025-05-06T20:10:10.8825497Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-05-06T20:10:10.9093827Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 33.5 MB/s eta 0:00:00 2025-05-06T20:10:10.9294947Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-05-06T20:10:10.9529340Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-05-06T20:10:10.9596788Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 89.3 MB/s eta 0:00:00 2025-05-06T20:10:10.9800743Z Downloading wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (89 kB) 2025-05-06T20:10:10.9852021Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 22.0 MB/s eta 0:00:00 2025-05-06T20:10:11.0052542Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-05-06T20:10:11.0095749Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 38.2 MB/s eta 0:00:00 2025-05-06T20:10:11.2988000Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-05-06T20:10:11.8208429Z 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.2 2025-05-06T20:10:11.8923597Z ##[group]Run curr_branch="ciflow/trunk/147470" 2025-05-06T20:10:11.8923994Z curr_branch="ciflow/trunk/147470" 2025-05-06T20:10:11.8924270Z curr_ref_type="tag" 2025-05-06T20:10:11.8924526Z echo "Current branch is '$curr_branch'" 2025-05-06T20:10:11.8924789Z  2025-05-06T20:10:11.8924989Z python3 runner_determinator.py \ 2025-05-06T20:10:11.8925280Z  --github-token "$GITHUB_TOKEN" \ 2025-05-06T20:10:11.8925550Z  --github-issue "$ISSUE_NUMBER" \ 2025-05-06T20:10:11.8925836Z  --github-branch "$curr_branch" \ 2025-05-06T20:10:11.8926136Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-05-06T20:10:11.8926434Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-05-06T20:10:11.8926730Z  --github-ref-type "$curr_ref_type" \ 2025-05-06T20:10:11.8927249Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-05-06T20:10:11.8927601Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-05-06T20:10:11.8927944Z  --pr-number "${PR_NUMBER}" 2025-05-06T20:10:11.8959988Z shell: /usr/bin/bash -e {0} 2025-05-06T20:10:11.8960227Z env: 2025-05-06T20:10:11.8960784Z GITHUB_TOKEN: *** 2025-05-06T20:10:11.8961010Z ISSUE_NUMBER: 5132 2025-05-06T20:10:11.8961226Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:10:11.8961482Z ISSUE_OWNER: 2025-05-06T20:10:11.8961672Z CHECK_EXPERIMENTS: 2025-05-06T20:10:11.8961872Z PR_NUMBER: 2025-05-06T20:10:11.8962051Z ##[endgroup] 2025-05-06T20:10:11.9012284Z Current branch is 'ciflow/trunk/147470' 2025-05-06T20:10:14.1917551Z INFO : Based on rollout percentage of 100%, enabling experiment ephemeral. 2025-05-06T20:10:14.1918534Z INFO : Setting output: label-type='ephemeral.' 2025-05-06T20:10:14.2217736Z Evaluate and set job outputs 2025-05-06T20:10:14.2224064Z Set output 'label-type' 2025-05-06T20:10:14.2225798Z Cleaning up orphan processes