2025-05-06T20:02:41.7629829Z Current runner version: '2.323.0' 2025-05-06T20:02:41.7655666Z ##[group]Operating System 2025-05-06T20:02:41.7656773Z Ubuntu 2025-05-06T20:02:41.7657231Z 24.04.2 2025-05-06T20:02:41.7657692Z LTS 2025-05-06T20:02:41.7658211Z ##[endgroup] 2025-05-06T20:02:41.7658724Z ##[group]Runner Image 2025-05-06T20:02:41.7659286Z Image: ubuntu-24.04 2025-05-06T20:02:41.7659884Z Version: 20250427.1.0 2025-05-06T20:02:41.7661233Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250427.1/images/ubuntu/Ubuntu2404-Readme.md 2025-05-06T20:02:41.7662645Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250427.1 2025-05-06T20:02:41.7663624Z ##[endgroup] 2025-05-06T20:02:41.7664092Z ##[group]Runner Image Provisioner 2025-05-06T20:02:41.7664683Z 2.0.422.1 2025-05-06T20:02:41.7665160Z ##[endgroup] 2025-05-06T20:02:41.7666007Z ##[group]GITHUB_TOKEN Permissions 2025-05-06T20:02:41.7667833Z Metadata: read 2025-05-06T20:02:41.7668387Z ##[endgroup] 2025-05-06T20:02:41.7671063Z Secret source: Actions 2025-05-06T20:02:41.7671955Z Prepare workflow directory 2025-05-06T20:02:41.8178500Z Prepare all required actions 2025-05-06T20:02:41.8234568Z Complete job name: get-label-type / runner-determinator 2025-05-06T20:02:42.3835462Z ##[group]Run cat < runner_determinator.py 2025-05-06T20:02:42.3838041Z cat < runner_determinator.py 2025-05-06T20:02:42.3838820Z # flake8: noqa: G004 2025-05-06T20:02:42.3839359Z  2025-05-06T20:02:42.3840152Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-05-06T20:02:42.3841754Z # must be kept in sync. You can do it easily by running the following command: 2025-05-06T20:02:42.3842754Z # python .github/scripts/update_runner_determinator.py 2025-05-06T20:02:42.3843457Z  2025-05-06T20:02:42.3843969Z """ 2025-05-06T20:02:42.3844712Z This runner determinator is used to determine which set of runners to run a 2025-05-06T20:02:42.3845750Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-05-06T20:02:42.3847013Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-05-06T20:02:42.3847986Z of which runners should be used to run which job. 2025-05-06T20:02:42.3848726Z  2025-05-06T20:02:42.3849451Z The configuration has two parts, the settings and a list of opted-in users, 2025-05-06T20:02:42.3850722Z separated by a line containing "---". If the line is not present, the 2025-05-06T20:02:42.3851838Z settings are considered to be empty with only the second part, the user 2025-05-06T20:02:42.3852695Z list, defined. 2025-05-06T20:02:42.3853228Z  2025-05-06T20:02:42.3853874Z The first part is a YAML block that defines the rollout settings. This can be 2025-05-06T20:02:42.3855026Z used to define any settings that are needed to determine which runners to use. 2025-05-06T20:02:42.3856041Z It's fields are defined by the RolloutSettings class below. 2025-05-06T20:02:42.3856747Z  2025-05-06T20:02:42.3857571Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-05-06T20:02:42.3858575Z The user list is also a comma separated list of additional features or 2025-05-06T20:02:42.3859470Z experiments which the user could be opted in to. 2025-05-06T20:02:42.3860196Z  2025-05-06T20:02:42.3861102Z The user list has the following rules: 2025-05-06T20:02:42.3861748Z  2025-05-06T20:02:42.3862732Z - Users are GitHub usernames, which must start with the @ prefix 2025-05-06T20:02:42.3863763Z - Each user is also a comma-separated list of features/experiments to enable 2025-05-06T20:02:42.3864659Z - A "#" prefix opts the user out of all experiments 2025-05-06T20:02:42.3865398Z  2025-05-06T20:02:42.3866013Z Example config: 2025-05-06T20:02:42.3866906Z  # A list of experiments that can be opted into. 2025-05-06T20:02:42.3867850Z  # This defines the behavior they'll induce when opted into. 2025-05-06T20:02:42.3868598Z  # Expected syntax is: 2025-05-06T20:02:42.3869429Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-05-06T20:02:42.3870750Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-05-06T20:02:42.3871630Z  2025-05-06T20:02:42.3872108Z  experiments: 2025-05-06T20:02:42.3872688Z  lf: 2025-05-06T20:02:42.3873241Z  rollout_percent: 25 2025-05-06T20:02:42.3873827Z  all_branches: false 2025-05-06T20:02:42.3874468Z  default: true 2025-05-06T20:02:42.3875053Z  --- 2025-05-06T20:02:42.3875505Z  2025-05-06T20:02:42.3876040Z  # Opt-ins: 2025-05-06T20:02:42.3876797Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-05-06T20:02:42.3877991Z  # and specifying experiments to enable in a comma-separated list. 2025-05-06T20:02:42.3879033Z  # To always opt out of an experiment, prefix it with a "-". 2025-05-06T20:02:42.3879841Z  # Experiments should be from the above list. 2025-05-06T20:02:42.3880659Z  2025-05-06T20:02:42.3881117Z  @User1,-lf,split_build 2025-05-06T20:02:42.3881796Z  @User2,lf 2025-05-06T20:02:42.3882305Z  @User3,split_build 2025-05-06T20:02:42.3882864Z """ 2025-05-06T20:02:42.3883378Z  2025-05-06T20:02:42.3883850Z import json 2025-05-06T20:02:42.3884396Z import logging 2025-05-06T20:02:42.3884955Z import os 2025-05-06T20:02:42.3885473Z import random 2025-05-06T20:02:42.3885967Z import re 2025-05-06T20:02:42.3886541Z import sys 2025-05-06T20:02:42.3887096Z from argparse import ArgumentParser 2025-05-06T20:02:42.3887847Z from collections.abc import Iterable 2025-05-06T20:02:42.3888592Z from functools import cache 2025-05-06T20:02:42.3889220Z from logging import LogRecord 2025-05-06T20:02:42.3889887Z from typing import Any, NamedTuple 2025-05-06T20:02:42.3967426Z from urllib.request import Request, urlopen 2025-05-06T20:02:42.3968358Z  2025-05-06T20:02:42.3968754Z import yaml 2025-05-06T20:02:42.3969229Z from github import Auth, Github 2025-05-06T20:02:42.3969809Z from github.Issue import Issue 2025-05-06T20:02:42.3970331Z  2025-05-06T20:02:42.3971012Z  2025-05-06T20:02:42.3971502Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-05-06T20:02:42.3972296Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-05-06T20:02:42.3973303Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-05-06T20:02:42.3974049Z  2025-05-06T20:02:42.3974525Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-05-06T20:02:42.3975159Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-05-06T20:02:42.3975758Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-05-06T20:02:42.3976402Z OPT_OUT_LABEL = "no-runner-experiments" 2025-05-06T20:02:42.3976969Z  2025-05-06T20:02:42.3977393Z SETTING_EXPERIMENTS = "experiments" 2025-05-06T20:02:42.3977935Z  2025-05-06T20:02:42.3978339Z LF_FLEET_EXPERIMENT = "lf" 2025-05-06T20:02:42.3978862Z CANARY_FLEET_SUFFIX = ".c" 2025-05-06T20:02:42.3979353Z  2025-05-06T20:02:42.3979717Z  2025-05-06T20:02:42.3980135Z class Experiment(NamedTuple): 2025-05-06T20:02:42.3981012Z  rollout_perc: float = ( 2025-05-06T20:02:42.3981760Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-05-06T20:02:42.3982836Z  ) 2025-05-06T20:02:42.3983267Z  all_branches: bool = ( 2025-05-06T20:02:42.3983997Z  False # If True, the experiment is also enabled on the exception branches 2025-05-06T20:02:42.3984805Z  ) 2025-05-06T20:02:42.3985223Z  default: bool = ( 2025-05-06T20:02:42.3985893Z  True # If True, the experiment is enabled by default for all queries 2025-05-06T20:02:42.3986588Z  ) 2025-05-06T20:02:42.3986989Z  2025-05-06T20:02:42.3987401Z  # Add more fields as needed 2025-05-06T20:02:42.3987926Z  2025-05-06T20:02:42.3988296Z  2025-05-06T20:02:42.3988703Z class Settings(NamedTuple): 2025-05-06T20:02:42.3989212Z  """ 2025-05-06T20:02:42.3989746Z  Settings for the experiments that can be opted into. 2025-05-06T20:02:42.3990377Z  """ 2025-05-06T20:02:42.3991123Z  2025-05-06T20:02:42.3991578Z  experiments: dict[str, Experiment] = {} 2025-05-06T20:02:42.3992146Z  2025-05-06T20:02:42.3992667Z  2025-05-06T20:02:42.3993139Z class ColorFormatter(logging.Formatter): 2025-05-06T20:02:42.3993847Z  """Color codes the log messages based on the log level""" 2025-05-06T20:02:42.3994485Z  2025-05-06T20:02:42.3994881Z  COLORS = { 2025-05-06T20:02:42.3995370Z  "WARNING": "\033[33m", # Yellow 2025-05-06T20:02:42.3995925Z  "ERROR": "\033[31m", # Red 2025-05-06T20:02:42.3996480Z  "CRITICAL": "\033[31m", # Red 2025-05-06T20:02:42.3997044Z  "INFO": "\033[0m", # Reset 2025-05-06T20:02:42.3997601Z  "DEBUG": "\033[0m", # Reset 2025-05-06T20:02:42.3998114Z  } 2025-05-06T20:02:42.3998504Z  2025-05-06T20:02:42.3998957Z  def format(self, record: LogRecord) -> str: 2025-05-06T20:02:42.3999786Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-05-06T20:02:42.4000776Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-05-06T20:02:42.4001415Z  return super().format(record) 2025-05-06T20:02:42.4001942Z  2025-05-06T20:02:42.4002305Z  2025-05-06T20:02:42.4002738Z handler = logging.StreamHandler() 2025-05-06T20:02:42.4003533Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-05-06T20:02:42.4004305Z  2025-05-06T20:02:42.4004806Z log = logging.getLogger(os.path.basename(__file__)) 2025-05-06T20:02:42.4005452Z log.addHandler(handler) 2025-05-06T20:02:42.4005972Z log.setLevel(logging.INFO) 2025-05-06T20:02:42.4006470Z  2025-05-06T20:02:42.4006843Z  2025-05-06T20:02:42.4007336Z def set_github_output(key: str, value: str) -> None: 2025-05-06T20:02:42.4007969Z  """ 2025-05-06T20:02:42.4008550Z  Defines outputs of the github action that invokes this script 2025-05-06T20:02:42.4009229Z  """ 2025-05-06T20:02:42.4009656Z  if not GITHUB_OUTPUT: 2025-05-06T20:02:42.4011057Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-05-06T20:02:42.4012278Z  log.warning( 2025-05-06T20:02:42.4013212Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-05-06T20:02:42.4014209Z  ) 2025-05-06T20:02:42.4014700Z  print(f"::set-output name={key}::{value}") 2025-05-06T20:02:42.4015284Z  return 2025-05-06T20:02:42.4015718Z  2025-05-06T20:02:42.4016152Z  with open(GITHUB_OUTPUT, "a") as f: 2025-05-06T20:02:42.4016952Z  log.info(f"Setting output: {key}='{value}'") 2025-05-06T20:02:42.4017586Z  f.write(f"{key}={value}\n") 2025-05-06T20:02:42.4018116Z  2025-05-06T20:02:42.4018495Z  2025-05-06T20:02:42.4019046Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-05-06T20:02:42.4019764Z  return frozenset( 2025-05-06T20:02:42.4020669Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-05-06T20:02:42.4021479Z  ) 2025-05-06T20:02:42.4021875Z  2025-05-06T20:02:42.4022247Z  2025-05-06T20:02:42.4022644Z def parse_args() -> Any: 2025-05-06T20:02:42.4023312Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-05-06T20:02:42.4024252Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-05-06T20:02:42.4025068Z  parser.add_argument( 2025-05-06T20:02:42.4025619Z  "--github-issue-repo", 2025-05-06T20:02:42.4026151Z  type=str, 2025-05-06T20:02:42.4026637Z  required=False, 2025-05-06T20:02:42.4027316Z  default="pytorch/test-infra", 2025-05-06T20:02:42.4027944Z  help="GitHub repo to get the issue", 2025-05-06T20:02:42.4028503Z  ) 2025-05-06T20:02:42.4028924Z  parser.add_argument( 2025-05-06T20:02:42.4029448Z  "--github-repo", 2025-05-06T20:02:42.4029943Z  type=str, 2025-05-06T20:02:42.4030799Z  required=True, 2025-05-06T20:02:42.4031375Z  help="GitHub repo where CI is running", 2025-05-06T20:02:42.4031946Z  ) 2025-05-06T20:02:42.4032366Z  parser.add_argument( 2025-05-06T20:02:42.4033061Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-05-06T20:02:42.4033765Z  ) 2025-05-06T20:02:42.4034186Z  parser.add_argument( 2025-05-06T20:02:42.4034901Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-05-06T20:02:42.4035627Z  ) 2025-05-06T20:02:42.4036045Z  parser.add_argument( 2025-05-06T20:02:42.4036760Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-05-06T20:02:42.4037505Z  ) 2025-05-06T20:02:42.4037929Z  parser.add_argument( 2025-05-06T20:02:42.4038678Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-05-06T20:02:42.4039435Z  ) 2025-05-06T20:02:42.4039876Z  parser.add_argument( 2025-05-06T20:02:42.4040401Z  "--github-ref-type", 2025-05-06T20:02:42.4041025Z  type=str, 2025-05-06T20:02:42.4041503Z  required=True, 2025-05-06T20:02:42.4042080Z  help="Current GitHub ref type, branch or tag", 2025-05-06T20:02:42.4042680Z  ) 2025-05-06T20:02:42.4043106Z  parser.add_argument( 2025-05-06T20:02:42.4043644Z  "--eligible-experiments", 2025-05-06T20:02:42.4044231Z  type=_str_comma_separated_to_set, 2025-05-06T20:02:42.4044798Z  required=False, 2025-05-06T20:02:42.4045296Z  default="", 2025-05-06T20:02:42.4046228Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-05-06T20:02:42.4047207Z  ) 2025-05-06T20:02:42.4047634Z  parser.add_argument( 2025-05-06T20:02:42.4048143Z  "--pr-number", 2025-05-06T20:02:42.4048632Z  type=str, 2025-05-06T20:02:42.4049109Z  required=False, 2025-05-06T20:02:42.4049606Z  default="", 2025-05-06T20:02:42.4050172Z  help="the optional PR number where this is run", 2025-05-06T20:02:42.4051212Z  ) 2025-05-06T20:02:42.4051620Z  2025-05-06T20:02:42.4052034Z  return parser.parse_args() 2025-05-06T20:02:42.4052565Z  2025-05-06T20:02:42.4052952Z  2025-05-06T20:02:42.4053608Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-05-06T20:02:42.4054436Z  auth = Auth.Token(github_token) 2025-05-06T20:02:42.4055014Z  return Github(auth=auth) 2025-05-06T20:02:42.4055513Z  2025-05-06T20:02:42.4055885Z  2025-05-06T20:02:42.4056592Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-05-06T20:02:42.4057452Z  repo = gh.get_repo(repo) 2025-05-06T20:02:42.4058033Z  return repo.get_issue(number=issue_num) 2025-05-06T20:02:42.4058592Z  2025-05-06T20:02:42.4058964Z  2025-05-06T20:02:42.4059364Z def get_potential_pr_author( 2025-05-06T20:02:42.4060101Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-05-06T20:02:42.4061138Z ) -> str: 2025-05-06T20:02:42.4061904Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-05-06T20:02:42.4062814Z  # Fetch the actual username from the original PR. The PR number is 2025-05-06T20:02:42.4063637Z  # embedded in the tag name: ciflow// 2025-05-06T20:02:42.4064258Z  2025-05-06T20:02:42.4064682Z  gh = get_gh_client(github_token) 2025-05-06T20:02:42.4065222Z  2025-05-06T20:02:42.4065746Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-05-06T20:02:42.4066457Z  split_tag = ref_name.split("/") 2025-05-06T20:02:42.4067016Z  if ( 2025-05-06T20:02:42.4067481Z  len(split_tag) == 3 2025-05-06T20:02:42.4068039Z  and split_tag[0] == "ciflow" 2025-05-06T20:02:42.4068636Z  and split_tag[2].isnumeric() 2025-05-06T20:02:42.4069175Z  ): 2025-05-06T20:02:42.4069648Z  pr_number = split_tag[2] 2025-05-06T20:02:42.4070205Z  try: 2025-05-06T20:02:42.4071380Z  repository = gh.get_repo(repo) 2025-05-06T20:02:42.4072063Z  pull = repository.get_pull(number=int(pr_number)) 2025-05-06T20:02:42.4072729Z  except Exception as e: 2025-05-06T20:02:42.4073314Z  raise Exception( # noqa: TRY002 2025-05-06T20:02:42.4074034Z  f"issue with pull request {pr_number} from repo {repository}" 2025-05-06T20:02:42.4074728Z  ) from e 2025-05-06T20:02:42.4075356Z  return pull.user.login # type: ignore[no-any-return] 2025-05-06T20:02:42.4076124Z  # In all other cases, return the original input username 2025-05-06T20:02:42.4076801Z  return username 2025-05-06T20:02:42.4077266Z  2025-05-06T20:02:42.4077648Z  2025-05-06T20:02:42.4078122Z def is_exception_branch(branch: str) -> bool: 2025-05-06T20:02:42.4078716Z  """ 2025-05-06T20:02:42.4079450Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-05-06T20:02:42.4080278Z  """ 2025-05-06T20:02:42.4081102Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-05-06T20:02:42.4081809Z  2025-05-06T20:02:42.4082184Z  2025-05-06T20:02:42.4082611Z def load_yaml(yaml_text: str) -> Any: 2025-05-06T20:02:42.4083158Z  try: 2025-05-06T20:02:42.4083605Z  data = yaml.safe_load(yaml_text) 2025-05-06T20:02:42.4084184Z  return data 2025-05-06T20:02:42.4084678Z  except yaml.YAMLError: 2025-05-06T20:02:42.4085398Z  log.exception("Error loading YAML") 2025-05-06T20:02:42.4085960Z  raise 2025-05-06T20:02:42.4086385Z  2025-05-06T20:02:42.4086767Z  2025-05-06T20:02:42.4087436Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-05-06T20:02:42.4088227Z  """ 2025-05-06T20:02:42.4088923Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-05-06T20:02:42.4089708Z  2025-05-06T20:02:42.4090295Z  If the issue body contains "---" then the text above that is the settings 2025-05-06T20:02:42.4091238Z  and the text below is the list of opted in users. 2025-05-06T20:02:42.4091842Z  2025-05-06T20:02:42.4092460Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-05-06T20:02:42.4093229Z  """ 2025-05-06T20:02:42.4093740Z  rollout_state_parts = rollout_state.split("---") 2025-05-06T20:02:42.4094396Z  if len(rollout_state_parts) >= 2: 2025-05-06T20:02:42.4095197Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-05-06T20:02:42.4095853Z  else: 2025-05-06T20:02:42.4096294Z  return "", rollout_state 2025-05-06T20:02:42.4096802Z  2025-05-06T20:02:42.4097177Z  2025-05-06T20:02:42.4097615Z class UserOptins(dict[str, list[str]]): 2025-05-06T20:02:42.4098180Z  """ 2025-05-06T20:02:42.4098791Z  Dictionary of users with a list of features they have opted into 2025-05-06T20:02:42.4099493Z  """ 2025-05-06T20:02:42.4099893Z  2025-05-06T20:02:42.4100257Z  2025-05-06T20:02:42.4101244Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-05-06T20:02:42.4101965Z  """ 2025-05-06T20:02:42.4102761Z  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:02:42.4103659Z  2025-05-06T20:02:42.4104513Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-05-06T20:02:42.4105580Z  - Example line: "@User1,lf,split_build" 2025-05-06T20:02:42.4106321Z  - A "#" prefix indicates the user is opted out of all experiments 2025-05-06T20:02:42.4107000Z  2025-05-06T20:02:42.4107368Z  2025-05-06T20:02:42.4107731Z  """ 2025-05-06T20:02:42.4108155Z  optins = UserOptins() 2025-05-06T20:02:42.4108719Z  for user in user_optin_text.split("\n"): 2025-05-06T20:02:42.4109341Z  user = user.strip("\r\n\t -") 2025-05-06T20:02:42.4109952Z  if not user or not user.startswith("@"): 2025-05-06T20:02:42.4110671Z  # Not a valid user. Skip 2025-05-06T20:02:42.4111224Z  continue 2025-05-06T20:02:42.4111678Z  2025-05-06T20:02:42.4112056Z  if user: 2025-05-06T20:02:42.4112575Z  usr_name = user.split(",")[0].strip("@") 2025-05-06T20:02:42.4113326Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-05-06T20:02:42.4114011Z  2025-05-06T20:02:42.4114401Z  return optins 2025-05-06T20:02:42.4114847Z  2025-05-06T20:02:42.4115214Z  2025-05-06T20:02:42.4115742Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-05-06T20:02:42.4116407Z  """ 2025-05-06T20:02:42.4116883Z  Check if the experiment name is valid. 2025-05-06T20:02:42.4117488Z  A valid name: 2025-05-06T20:02:42.4118217Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-05-06T20:02:42.4119207Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-05-06T20:02:42.4120111Z  - Cannot contain spaces 2025-05-06T20:02:42.4120725Z  """ 2025-05-06T20:02:42.4121120Z  2025-05-06T20:02:42.4121617Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-05-06T20:02:42.4122379Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-05-06T20:02:42.4123024Z  2025-05-06T20:02:42.4123385Z  if valid: 2025-05-06T20:02:42.4123825Z  return True 2025-05-06T20:02:42.4124273Z  2025-05-06T20:02:42.4124646Z  log.error( 2025-05-06T20:02:42.4126171Z  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:02:42.4127785Z  ) 2025-05-06T20:02:42.4128191Z  return False 2025-05-06T20:02:42.4128621Z  2025-05-06T20:02:42.4128981Z  2025-05-06T20:02:42.4129644Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-05-06T20:02:42.4130606Z  """ 2025-05-06T20:02:42.4131372Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-05-06T20:02:42.4132135Z  """ 2025-05-06T20:02:42.4132530Z  try: 2025-05-06T20:02:42.4132946Z  if settings_text: 2025-05-06T20:02:42.4133754Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-05-06T20:02:42.4134589Z  # for easy reading 2025-05-06T20:02:42.4135483Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-05-06T20:02:42.4136445Z  # the backtick character in shell commands. 2025-05-06T20:02:42.4137115Z  backtick = chr(96) # backtick character 2025-05-06T20:02:42.4137856Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-05-06T20:02:42.4138577Z  settings = load_yaml(settings_text) 2025-05-06T20:02:42.4139131Z  2025-05-06T20:02:42.4139773Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-05-06T20:02:42.4140659Z  experiments = {} 2025-05-06T20:02:42.4141161Z  2025-05-06T20:02:42.4141751Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-05-06T20:02:42.4142605Z  if not is_valid_experiment_name(exp_name): 2025-05-06T20:02:42.4143781Z  # 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:02:42.4144877Z  continue 2025-05-06T20:02:42.4145376Z  2025-05-06T20:02:42.4145780Z  valid_settings = {} 2025-05-06T20:02:42.4146369Z  for setting in exp_settings: 2025-05-06T20:02:42.4146982Z  if setting not in Experiment._fields: 2025-05-06T20:02:42.4147587Z  log.warning( 2025-05-06T20:02:42.4148359Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-05-06T20:02:42.4149114Z  ) 2025-05-06T20:02:42.4149605Z  else: 2025-05-06T20:02:42.4150188Z  valid_settings[setting] = exp_settings[setting] 2025-05-06T20:02:42.4150910Z  2025-05-06T20:02:42.4151422Z  experiments[exp_name] = Experiment(**valid_settings) 2025-05-06T20:02:42.4152126Z  return Settings(experiments) 2025-05-06T20:02:42.4152807Z  2025-05-06T20:02:42.4153195Z  except Exception: 2025-05-06T20:02:42.4153768Z  log.exception("Failed to parse settings") 2025-05-06T20:02:42.4154354Z  2025-05-06T20:02:42.4154739Z  return Settings() 2025-05-06T20:02:42.4155199Z  2025-05-06T20:02:42.4155564Z  2025-05-06T20:02:42.4156048Z def parse_settings(rollout_state: str) -> Settings: 2025-05-06T20:02:42.4156682Z  """ 2025-05-06T20:02:42.4157185Z  Parse settings, if any, from the rollout state. 2025-05-06T20:02:42.4157772Z  2025-05-06T20:02:42.4158361Z  If the issue body contains "---" then the text above that is the settings 2025-05-06T20:02:42.4159178Z  and the text below is the list of opted in users. 2025-05-06T20:02:42.4159780Z  2025-05-06T20:02:42.4160524Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-05-06T20:02:42.4161342Z  """ 2025-05-06T20:02:42.4161968Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:02:42.4162909Z  return parse_settings_from_text(settings_text) 2025-05-06T20:02:42.4163507Z  2025-05-06T20:02:42.4163870Z  2025-05-06T20:02:42.4164362Z def parse_users(rollout_state: str) -> UserOptins: 2025-05-06T20:02:42.4164971Z  """ 2025-05-06T20:02:42.4165423Z  Parse users from the rollout state. 2025-05-06T20:02:42.4165963Z  2025-05-06T20:02:42.4166324Z  """ 2025-05-06T20:02:42.4166922Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:02:42.4167738Z  return parse_user_opt_in_from_text(users_text) 2025-05-06T20:02:42.4168325Z  2025-05-06T20:02:42.4168683Z  2025-05-06T20:02:42.4169342Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:02:42.4170143Z  """ 2025-05-06T20:02:42.4170729Z  Check if a user is opted into an experiment 2025-05-06T20:02:42.4171316Z  """ 2025-05-06T20:02:42.4171829Z  return experiment_name in user_optins.get(user, []) 2025-05-06T20:02:42.4172447Z  2025-05-06T20:02:42.4172806Z  2025-05-06T20:02:42.4173477Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:02:42.4174276Z  """ 2025-05-06T20:02:42.4174813Z  Check if a user explicitly opted out of an experiment 2025-05-06T20:02:42.4175442Z  """ 2025-05-06T20:02:42.4176008Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-05-06T20:02:42.4176765Z  experiment_optout = "-" + experiment_name 2025-05-06T20:02:42.4177462Z  if experiment_optout not in user_optins.get(user, []): 2025-05-06T20:02:42.4178115Z  return False 2025-05-06T20:02:42.4178565Z  2025-05-06T20:02:42.4179075Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-05-06T20:02:42.4179715Z  log.warning( 2025-05-06T20:02:42.4180698Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-05-06T20:02:42.4181619Z  ) 2025-05-06T20:02:42.4182016Z  2025-05-06T20:02:42.4182392Z  return True 2025-05-06T20:02:42.4182818Z  2025-05-06T20:02:42.4183179Z  2025-05-06T20:02:42.4183566Z def get_runner_prefix( 2025-05-06T20:02:42.4184066Z  rollout_state: str, 2025-05-06T20:02:42.4184599Z  workflow_requestors: Iterable[str], 2025-05-06T20:02:42.4185153Z  branch: str, 2025-05-06T20:02:42.4185746Z  eligible_experiments: frozenset[str] = frozenset(), 2025-05-06T20:02:42.4186543Z  is_canary: bool = False, 2025-05-06T20:02:42.4187054Z ) -> str: 2025-05-06T20:02:42.4187536Z  settings = parse_settings(rollout_state) 2025-05-06T20:02:42.4188176Z  user_optins = parse_users(rollout_state) 2025-05-06T20:02:42.4188728Z  2025-05-06T20:02:42.4189114Z  fleet_prefix = "" 2025-05-06T20:02:42.4189604Z  prefixes = [] 2025-05-06T20:02:42.4190303Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-05-06T20:02:42.4191417Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-05-06T20:02:42.4192180Z  log.info( 2025-05-06T20:02:42.4192936Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-05-06T20:02:42.4193726Z  ) 2025-05-06T20:02:42.4194157Z  continue 2025-05-06T20:02:42.4194618Z  2025-05-06T20:02:42.4195033Z  if eligible_experiments: 2025-05-06T20:02:42.4195805Z  if experiment_name not in eligible_experiments: 2025-05-06T20:02:42.4196510Z  exp_list = ", ".join(eligible_experiments) 2025-05-06T20:02:42.4197103Z  log.info( 2025-05-06T20:02:42.4197970Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-05-06T20:02:42.4198845Z  ) 2025-05-06T20:02:42.4199306Z  continue 2025-05-06T20:02:42.4199849Z  elif not experiment_settings.default: 2025-05-06T20:02:42.4200509Z  log.info( 2025-05-06T20:02:42.4201242Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-05-06T20:02:42.4202017Z  ) 2025-05-06T20:02:42.4202447Z  continue 2025-05-06T20:02:42.4202905Z  2025-05-06T20:02:42.4203415Z  # Is any workflow_requestor opted out to this experiment? 2025-05-06T20:02:42.4204095Z  opted_out_users = [ 2025-05-06T20:02:42.4204616Z  requestor 2025-05-06T20:02:42.4205145Z  for requestor in workflow_requestors 2025-05-06T20:02:42.4205880Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-05-06T20:02:42.4206545Z  ] 2025-05-06T20:02:42.4206947Z  2025-05-06T20:02:42.4207339Z  if opted_out_users: 2025-05-06T20:02:42.4207848Z  log.info( 2025-05-06T20:02:42.4208551Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-05-06T20:02:42.4209279Z  ) 2025-05-06T20:02:42.4209712Z  continue 2025-05-06T20:02:42.4210154Z  2025-05-06T20:02:42.4210771Z  # Is any workflow_requestor opted in to this experiment? 2025-05-06T20:02:42.4211441Z  opted_in_users = [ 2025-05-06T20:02:42.4211947Z  requestor 2025-05-06T20:02:42.4212506Z  for requestor in workflow_requestors 2025-05-06T20:02:42.4213229Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-05-06T20:02:42.4213897Z  ] 2025-05-06T20:02:42.4214297Z  2025-05-06T20:02:42.4214709Z  enabled = False 2025-05-06T20:02:42.4215206Z  if opted_in_users: 2025-05-06T20:02:42.4215715Z  log.info( 2025-05-06T20:02:42.4216400Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-05-06T20:02:42.4217126Z  ) 2025-05-06T20:02:42.4217566Z  enabled = True 2025-05-06T20:02:42.4218043Z  2025-05-06T20:02:42.4218634Z  elif experiment_settings.rollout_perc: 2025-05-06T20:02:42.4219552Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-05-06T20:02:42.4220678Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-05-06T20:02:42.4221371Z  log.info( 2025-05-06T20:02:42.4222309Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-05-06T20:02:42.4223268Z  ) 2025-05-06T20:02:42.4223729Z  enabled = True 2025-05-06T20:02:42.4224231Z  2025-05-06T20:02:42.4224618Z  if enabled: 2025-05-06T20:02:42.4225108Z  label = experiment_name 2025-05-06T20:02:42.4225733Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-05-06T20:02:42.4226619Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-05-06T20:02:42.4227686Z  # - If it's enabled, then we always list it's prefix first 2025-05-06T20:02:42.4228517Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-05-06T20:02:42.4229229Z  if is_canary: 2025-05-06T20:02:42.4229783Z  label += CANARY_FLEET_SUFFIX 2025-05-06T20:02:42.4230372Z  fleet_prefix = label 2025-05-06T20:02:42.4231016Z  else: 2025-05-06T20:02:42.4231524Z  prefixes.append(label) 2025-05-06T20:02:42.4232070Z  2025-05-06T20:02:42.4232465Z  if len(prefixes) > 1: 2025-05-06T20:02:42.4232971Z  log.error( 2025-05-06T20:02:42.4234106Z  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:02:42.4235270Z  ) 2025-05-06T20:02:42.4235716Z  prefixes = prefixes[:1] 2025-05-06T20:02:42.4236230Z  2025-05-06T20:02:42.4236628Z  # Fleet always comes first 2025-05-06T20:02:42.4237159Z  if fleet_prefix: 2025-05-06T20:02:42.4237675Z  prefixes.insert(0, fleet_prefix) 2025-05-06T20:02:42.4238224Z  2025-05-06T20:02:42.4238722Z  return ".".join(prefixes) + "." if prefixes else "" 2025-05-06T20:02:42.4239334Z  2025-05-06T20:02:42.4239695Z  2025-05-06T20:02:42.4240380Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-05-06T20:02:42.4241292Z  """ 2025-05-06T20:02:42.4241937Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-05-06T20:02:42.4242693Z  2025-05-06T20:02:42.4243308Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-05-06T20:02:42.4244057Z  """ 2025-05-06T20:02:42.4244506Z  gh = get_gh_client(github_token) 2025-05-06T20:02:42.4245106Z  issue = get_issue(gh, repo, issue_num) 2025-05-06T20:02:42.4245816Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-05-06T20:02:42.4246455Z  2025-05-06T20:02:42.4246831Z  2025-05-06T20:02:42.4247464Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-05-06T20:02:42.4248277Z  for _ in range(num_retries): 2025-05-06T20:02:42.4248799Z  try: 2025-05-06T20:02:42.4249284Z  req = Request(url=url, headers=headers) 2025-05-06T20:02:42.4250008Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-05-06T20:02:42.4250798Z  return json.loads(content) 2025-05-06T20:02:42.4251507Z  except Exception as e: 2025-05-06T20:02:42.4252111Z  log.warning(f"Could not download {url}: {e}") 2025-05-06T20:02:42.4252710Z  2025-05-06T20:02:42.4253322Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-05-06T20:02:42.4254081Z  return {} 2025-05-06T20:02:42.4254503Z  2025-05-06T20:02:42.4254866Z  2025-05-06T20:02:42.4255230Z @cache 2025-05-06T20:02:42.4255917Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-05-06T20:02:42.4256712Z  """ 2025-05-06T20:02:42.4257154Z  Dynamically get PR information 2025-05-06T20:02:42.4257689Z  """ 2025-05-06T20:02:42.4258241Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-05-06T20:02:42.4258910Z  headers = { 2025-05-06T20:02:42.4259438Z  "Accept": "application/vnd.github.v3+json", 2025-05-06T20:02:42.4260102Z  "Authorization": f"token {github_token}", 2025-05-06T20:02:42.4260775Z  } 2025-05-06T20:02:42.4261404Z  json_response: dict[str, Any] = download_json( 2025-05-06T20:02:42.4262083Z  url=f"{github_api}/issues/{pr_number}", 2025-05-06T20:02:42.4262667Z  headers=headers, 2025-05-06T20:02:42.4263265Z  ) 2025-05-06T20:02:42.4263650Z  2025-05-06T20:02:42.4264042Z  if not json_response: 2025-05-06T20:02:42.4264687Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-05-06T20:02:42.4265356Z  return {} 2025-05-06T20:02:42.4265809Z  2025-05-06T20:02:42.4266196Z  return json_response 2025-05-06T20:02:42.4266680Z  2025-05-06T20:02:42.4267039Z  2025-05-06T20:02:42.4267668Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-05-06T20:02:42.4268433Z  """ 2025-05-06T20:02:42.4269017Z  Dynamically get the latest list of labels from the pull request 2025-05-06T20:02:42.4269709Z  """ 2025-05-06T20:02:42.4270243Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-05-06T20:02:42.4271005Z  return { 2025-05-06T20:02:42.4271639Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-05-06T20:02:42.4272363Z  } 2025-05-06T20:02:42.4272747Z  2025-05-06T20:02:42.4273110Z  2025-05-06T20:02:42.4273487Z def main() -> None: 2025-05-06T20:02:42.4273974Z  args = parse_args() 2025-05-06T20:02:42.4274460Z  2025-05-06T20:02:42.4274907Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-05-06T20:02:42.4275492Z  2025-05-06T20:02:42.4275898Z  # Check if the PR is opt-out 2025-05-06T20:02:42.4276455Z  if args.pr_number: 2025-05-06T20:02:42.4277185Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-05-06T20:02:42.4277996Z  if OPT_OUT_LABEL in labels: 2025-05-06T20:02:42.4278537Z  log.info( 2025-05-06T20:02:42.4279314Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-05-06T20:02:42.4280116Z  ) 2025-05-06T20:02:42.4280834Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:02:42.4281554Z  sys.exit() 2025-05-06T20:02:42.4282010Z  2025-05-06T20:02:42.4282375Z  try: 2025-05-06T20:02:42.4282860Z  rollout_state = get_rollout_state_from_issue( 2025-05-06T20:02:42.4283628Z  args.github_token, args.github_issue_repo, args.github_issue 2025-05-06T20:02:42.4284307Z  ) 2025-05-06T20:02:42.4284846Z  2025-05-06T20:02:42.4285277Z  username = get_potential_pr_author( 2025-05-06T20:02:42.4285851Z  args.github_token, 2025-05-06T20:02:42.4286391Z  args.github_repo, 2025-05-06T20:02:42.4286920Z  args.github_actor, 2025-05-06T20:02:42.4287472Z  args.github_ref_type, 2025-05-06T20:02:42.4288025Z  args.github_branch, 2025-05-06T20:02:42.4288533Z  ) 2025-05-06T20:02:42.4288932Z  2025-05-06T20:02:42.4289439Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-05-06T20:02:42.4290080Z  2025-05-06T20:02:42.4290619Z  runner_label_prefix = get_runner_prefix( 2025-05-06T20:02:42.4291216Z  rollout_state, 2025-05-06T20:02:42.4291759Z  (args.github_issue_owner, username), 2025-05-06T20:02:42.4292346Z  args.github_branch, 2025-05-06T20:02:42.4292911Z  args.eligible_experiments, 2025-05-06T20:02:42.4293464Z  is_canary, 2025-05-06T20:02:42.4294063Z  ) 2025-05-06T20:02:42.4294476Z  2025-05-06T20:02:42.4294874Z  except Exception as e: 2025-05-06T20:02:42.4295376Z  log.error( 2025-05-06T20:02:42.4296121Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-05-06T20:02:42.4296913Z  ) 2025-05-06T20:02:42.4297307Z  2025-05-06T20:02:42.4297878Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:02:42.4298567Z  2025-05-06T20:02:42.4298936Z  2025-05-06T20:02:42.4299319Z if __name__ == "__main__": 2025-05-06T20:02:42.4299816Z  main() 2025-05-06T20:02:42.4300220Z  2025-05-06T20:02:42.4300692Z EOF 2025-05-06T20:02:42.4301079Z  2025-05-06T20:02:42.4301480Z cat runner_determinator.py 2025-05-06T20:02:42.4586263Z shell: /usr/bin/bash -e {0} 2025-05-06T20:02:42.4587103Z env: 2025-05-06T20:02:42.4587916Z GITHUB_TOKEN: *** 2025-05-06T20:02:42.4588373Z ISSUE_NUMBER: 5132 2025-05-06T20:02:42.4588831Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:02:42.4589355Z ISSUE_OWNER: 2025-05-06T20:02:42.4589766Z CHECK_EXPERIMENTS: 2025-05-06T20:02:42.4590202Z PR_NUMBER: 2025-05-06T20:02:42.4590771Z ##[endgroup] 2025-05-06T20:02:42.4798328Z # flake8: noqa: G004 2025-05-06T20:02:42.4798760Z 2025-05-06T20:02:42.4799193Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-05-06T20:02:42.4800135Z # must be kept in sync. You can do it easily by running the following command: 2025-05-06T20:02:42.4801214Z # python .github/scripts/update_runner_determinator.py 2025-05-06T20:02:42.4801658Z 2025-05-06T20:02:42.4801818Z """ 2025-05-06T20:02:42.4802410Z This runner determinator is used to determine which set of runners to run a 2025-05-06T20:02:42.4803370Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-05-06T20:02:42.4804279Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-05-06T20:02:42.4805087Z of which runners should be used to run which job. 2025-05-06T20:02:42.4805490Z 2025-05-06T20:02:42.4805871Z The configuration has two parts, the settings and a list of opted-in users, 2025-05-06T20:02:42.4806755Z separated by a line containing "---". If the line is not present, the 2025-05-06T20:02:42.4807637Z settings are considered to be empty with only the second part, the user 2025-05-06T20:02:42.4808341Z list, defined. 2025-05-06T20:02:42.4808598Z 2025-05-06T20:02:42.4808961Z The first part is a YAML block that defines the rollout settings. This can be 2025-05-06T20:02:42.4809887Z used to define any settings that are needed to determine which runners to use. 2025-05-06T20:02:42.4811192Z It's fields are defined by the RolloutSettings class below. 2025-05-06T20:02:42.4811894Z 2025-05-06T20:02:42.4812311Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-05-06T20:02:42.4813195Z The user list is also a comma separated list of additional features or 2025-05-06T20:02:42.4813930Z experiments which the user could be opted in to. 2025-05-06T20:02:42.4814329Z 2025-05-06T20:02:42.4814529Z The user list has the following rules: 2025-05-06T20:02:42.4814875Z 2025-05-06T20:02:42.4815185Z - Users are GitHub usernames, which must start with the @ prefix 2025-05-06T20:02:42.4816060Z - Each user is also a comma-separated list of features/experiments to enable 2025-05-06T20:02:42.4816813Z - A "#" prefix opts the user out of all experiments 2025-05-06T20:02:42.4817206Z 2025-05-06T20:02:42.4817383Z Example config: 2025-05-06T20:02:42.4817830Z # A list of experiments that can be opted into. 2025-05-06T20:02:42.4818492Z # This defines the behavior they'll induce when opted into. 2025-05-06T20:02:42.4819118Z # Expected syntax is: 2025-05-06T20:02:42.4819757Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-05-06T20:02:42.4821060Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-05-06T20:02:42.4821706Z 2025-05-06T20:02:42.4821874Z experiments: 2025-05-06T20:02:42.4822268Z lf: 2025-05-06T20:02:42.4822651Z rollout_percent: 25 2025-05-06T20:02:42.4823107Z all_branches: false 2025-05-06T20:02:42.4823544Z default: true 2025-05-06T20:02:42.4823950Z --- 2025-05-06T20:02:42.4824155Z 2025-05-06T20:02:42.4824315Z # Opt-ins: 2025-05-06T20:02:42.4824894Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-05-06T20:02:42.4825742Z # and specifying experiments to enable in a comma-separated list. 2025-05-06T20:02:42.4826505Z # To always opt out of an experiment, prefix it with a "-". 2025-05-06T20:02:42.4827163Z # Experiments should be from the above list. 2025-05-06T20:02:42.4827540Z 2025-05-06T20:02:42.4827719Z @User1,-lf,split_build 2025-05-06T20:02:42.4828151Z @User2,lf 2025-05-06T20:02:42.4828529Z @User3,split_build 2025-05-06T20:02:42.4828934Z """ 2025-05-06T20:02:42.4829126Z 2025-05-06T20:02:42.4829287Z import json 2025-05-06T20:02:42.4829666Z import logging 2025-05-06T20:02:42.4830042Z import os 2025-05-06T20:02:42.4830407Z import random 2025-05-06T20:02:42.4831200Z import re 2025-05-06T20:02:42.4831581Z import sys 2025-05-06T20:02:42.4831992Z from argparse import ArgumentParser 2025-05-06T20:02:42.4832514Z from collections.abc import Iterable 2025-05-06T20:02:42.4833043Z from functools import cache 2025-05-06T20:02:42.4833516Z from logging import LogRecord 2025-05-06T20:02:42.4834007Z from typing import Any, NamedTuple 2025-05-06T20:02:42.4834536Z from urllib.request import Request, urlopen 2025-05-06T20:02:42.4834914Z 2025-05-06T20:02:42.4835081Z import yaml 2025-05-06T20:02:42.4835490Z from github import Auth, Github 2025-05-06T20:02:42.4835977Z from github.Issue import Issue 2025-05-06T20:02:42.4836283Z 2025-05-06T20:02:42.4836289Z 2025-05-06T20:02:42.4836760Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-05-06T20:02:42.4837456Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-05-06T20:02:42.4838311Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-05-06T20:02:42.4838873Z 2025-05-06T20:02:42.4839101Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-05-06T20:02:42.4839674Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-05-06T20:02:42.4840164Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-05-06T20:02:42.4840908Z OPT_OUT_LABEL = "no-runner-experiments" 2025-05-06T20:02:42.4841261Z 2025-05-06T20:02:42.4841467Z SETTING_EXPERIMENTS = "experiments" 2025-05-06T20:02:42.4841796Z 2025-05-06T20:02:42.4841982Z LF_FLEET_EXPERIMENT = "lf" 2025-05-06T20:02:42.4842439Z CANARY_FLEET_SUFFIX = ".c" 2025-05-06T20:02:42.4842719Z 2025-05-06T20:02:42.4842900Z 2025-05-06T20:02:42.4843099Z class Experiment(NamedTuple): 2025-05-06T20:02:42.4843588Z rollout_perc: float = ( 2025-05-06T20:02:42.4844224Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-05-06T20:02:42.4844908Z ) 2025-05-06T20:02:42.4845288Z all_branches: bool = ( 2025-05-06T20:02:42.4845908Z False # If True, the experiment is also enabled on the exception branches 2025-05-06T20:02:42.4846579Z ) 2025-05-06T20:02:42.4846927Z default: bool = ( 2025-05-06T20:02:42.4847490Z True # If True, the experiment is enabled by default for all queries 2025-05-06T20:02:42.4848125Z ) 2025-05-06T20:02:42.4848323Z 2025-05-06T20:02:42.4848501Z # Add more fields as needed 2025-05-06T20:02:42.4848803Z 2025-05-06T20:02:42.4848809Z 2025-05-06T20:02:42.4849000Z class Settings(NamedTuple): 2025-05-06T20:02:42.4849436Z """ 2025-05-06T20:02:42.4849887Z Settings for the experiments that can be opted into. 2025-05-06T20:02:42.4850665Z """ 2025-05-06T20:02:42.4850876Z 2025-05-06T20:02:42.4851092Z experiments: dict[str, Experiment] = {} 2025-05-06T20:02:42.4851459Z 2025-05-06T20:02:42.4851466Z 2025-05-06T20:02:42.4851838Z class ColorFormatter(logging.Formatter): 2025-05-06T20:02:42.4852493Z """Color codes the log messages based on the log level""" 2025-05-06T20:02:42.4852924Z 2025-05-06T20:02:42.4853095Z COLORS = { 2025-05-06T20:02:42.4853491Z "WARNING": "\033[33m", # Yellow 2025-05-06T20:02:42.4853994Z "ERROR": "\033[31m", # Red 2025-05-06T20:02:42.4854486Z "CRITICAL": "\033[31m", # Red 2025-05-06T20:02:42.4854985Z "INFO": "\033[0m", # Reset 2025-05-06T20:02:42.4855469Z "DEBUG": "\033[0m", # Reset 2025-05-06T20:02:42.4855935Z } 2025-05-06T20:02:42.4856128Z 2025-05-06T20:02:42.4856348Z def format(self, record: LogRecord) -> str: 2025-05-06T20:02:42.4857095Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-05-06T20:02:42.4857882Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-05-06T20:02:42.4858460Z return super().format(record) 2025-05-06T20:02:42.4858808Z 2025-05-06T20:02:42.4858815Z 2025-05-06T20:02:42.4859022Z handler = logging.StreamHandler() 2025-05-06T20:02:42.4859726Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-05-06T20:02:42.4860287Z 2025-05-06T20:02:42.4860747Z log = logging.getLogger(os.path.basename(__file__)) 2025-05-06T20:02:42.4861356Z log.addHandler(handler) 2025-05-06T20:02:42.4861801Z log.setLevel(logging.INFO) 2025-05-06T20:02:42.4862082Z 2025-05-06T20:02:42.4862088Z 2025-05-06T20:02:42.4862338Z def set_github_output(key: str, value: str) -> None: 2025-05-06T20:02:42.4862907Z """ 2025-05-06T20:02:42.4863415Z Defines outputs of the github action that invokes this script 2025-05-06T20:02:42.4864046Z """ 2025-05-06T20:02:42.4864422Z if not GITHUB_OUTPUT: 2025-05-06T20:02:42.4865735Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-05-06T20:02:42.4866921Z log.warning( 2025-05-06T20:02:42.4867841Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-05-06T20:02:42.4868761Z ) 2025-05-06T20:02:42.4880154Z print(f"::set-output name={key}::{value}") 2025-05-06T20:02:42.4881059Z return 2025-05-06T20:02:42.4881302Z 2025-05-06T20:02:42.4881513Z with open(GITHUB_OUTPUT, "a") as f: 2025-05-06T20:02:42.4882108Z log.info(f"Setting output: {key}='{value}'") 2025-05-06T20:02:42.4882693Z f.write(f"{key}={value}\n") 2025-05-06T20:02:42.4883019Z 2025-05-06T20:02:42.4883026Z 2025-05-06T20:02:42.4883340Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-05-06T20:02:42.4883987Z return frozenset( 2025-05-06T20:02:42.4884607Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-05-06T20:02:42.4885571Z ) 2025-05-06T20:02:42.4885778Z 2025-05-06T20:02:42.4885785Z 2025-05-06T20:02:42.4885969Z def parse_args() -> Any: 2025-05-06T20:02:42.4886528Z parser = ArgumentParser("Get dynamic rollout settings") 2025-05-06T20:02:42.4887393Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-05-06T20:02:42.4888160Z parser.add_argument( 2025-05-06T20:02:42.4888781Z "--github-issue-repo", 2025-05-06T20:02:42.4889232Z type=str, 2025-05-06T20:02:42.4889629Z required=False, 2025-05-06T20:02:42.4890098Z default="pytorch/test-infra", 2025-05-06T20:02:42.4891008Z help="GitHub repo to get the issue", 2025-05-06T20:02:42.4891544Z ) 2025-05-06T20:02:42.4892138Z parser.add_argument( 2025-05-06T20:02:42.4893258Z "--github-repo", 2025-05-06T20:02:42.4893715Z type=str, 2025-05-06T20:02:42.4894117Z required=True, 2025-05-06T20:02:42.4894581Z help="GitHub repo where CI is running", 2025-05-06T20:02:42.4895124Z ) 2025-05-06T20:02:42.4895496Z parser.add_argument( 2025-05-06T20:02:42.4896482Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-05-06T20:02:42.4897347Z ) 2025-05-06T20:02:42.4897749Z parser.add_argument( 2025-05-06T20:02:42.4898386Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-05-06T20:02:42.4899071Z ) 2025-05-06T20:02:42.4899441Z parser.add_argument( 2025-05-06T20:02:42.4900109Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-05-06T20:02:42.4901032Z ) 2025-05-06T20:02:42.4901411Z parser.add_argument( 2025-05-06T20:02:42.4902076Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-05-06T20:02:42.4902793Z ) 2025-05-06T20:02:42.4903193Z parser.add_argument( 2025-05-06T20:02:42.4903642Z "--github-ref-type", 2025-05-06T20:02:42.4904100Z type=str, 2025-05-06T20:02:42.4904503Z required=True, 2025-05-06T20:02:42.4904987Z help="Current GitHub ref type, branch or tag", 2025-05-06T20:02:42.4905531Z ) 2025-05-06T20:02:42.4905905Z parser.add_argument( 2025-05-06T20:02:42.4906363Z "--eligible-experiments", 2025-05-06T20:02:42.4906886Z type=_str_comma_separated_to_set, 2025-05-06T20:02:42.4907411Z required=False, 2025-05-06T20:02:42.4907822Z default="", 2025-05-06T20:02:42.4908682Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-05-06T20:02:42.4909958Z ) 2025-05-06T20:02:42.4910339Z parser.add_argument( 2025-05-06T20:02:42.4910993Z "--pr-number", 2025-05-06T20:02:42.4911417Z type=str, 2025-05-06T20:02:42.4911812Z required=False, 2025-05-06T20:02:42.4912421Z default="", 2025-05-06T20:02:42.4912906Z help="the optional PR number where this is run", 2025-05-06T20:02:42.4913467Z ) 2025-05-06T20:02:42.4913684Z 2025-05-06T20:02:42.4913882Z return parser.parse_args() 2025-05-06T20:02:42.4914189Z 2025-05-06T20:02:42.4914196Z 2025-05-06T20:02:42.4914598Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-05-06T20:02:42.4915369Z auth = Auth.Token(github_token) 2025-05-06T20:02:42.4915866Z return Github(auth=auth) 2025-05-06T20:02:42.4916167Z 2025-05-06T20:02:42.4916174Z 2025-05-06T20:02:42.4916621Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-05-06T20:02:42.4917426Z repo = gh.get_repo(repo) 2025-05-06T20:02:42.4917921Z return repo.get_issue(number=issue_num) 2025-05-06T20:02:42.4918291Z 2025-05-06T20:02:42.4918297Z 2025-05-06T20:02:42.4918494Z def get_potential_pr_author( 2025-05-06T20:02:42.4919123Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-05-06T20:02:42.4919806Z ) -> str: 2025-05-06T20:02:42.4920313Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-05-06T20:02:42.4921497Z # Fetch the actual username from the original PR. The PR number is 2025-05-06T20:02:42.4922282Z # embedded in the tag name: ciflow// 2025-05-06T20:02:42.4922709Z 2025-05-06T20:02:42.4922902Z gh = get_gh_client(github_token) 2025-05-06T20:02:42.4923242Z 2025-05-06T20:02:42.4923519Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-05-06T20:02:42.4924146Z split_tag = ref_name.split("/") 2025-05-06T20:02:42.4924656Z if ( 2025-05-06T20:02:42.4925040Z len(split_tag) == 3 2025-05-06T20:02:42.4925526Z and split_tag[0] == "ciflow" 2025-05-06T20:02:42.4926053Z and split_tag[2].isnumeric() 2025-05-06T20:02:42.4926552Z ): 2025-05-06T20:02:42.4982969Z pr_number = split_tag[2] 2025-05-06T20:02:42.4984118Z try: 2025-05-06T20:02:42.4984839Z repository = gh.get_repo(repo) 2025-05-06T20:02:42.4985618Z pull = repository.get_pull(number=int(pr_number)) 2025-05-06T20:02:42.4986296Z except Exception as e: 2025-05-06T20:02:42.4986973Z raise Exception( # noqa: TRY002 2025-05-06T20:02:42.4987975Z f"issue with pull request {pr_number} from repo {repository}" 2025-05-06T20:02:42.4988662Z ) from e 2025-05-06T20:02:42.4989200Z return pull.user.login # type: ignore[no-any-return] 2025-05-06T20:02:42.4989886Z # In all other cases, return the original input username 2025-05-06T20:02:42.4990766Z return username 2025-05-06T20:02:42.4991051Z 2025-05-06T20:02:42.4991059Z 2025-05-06T20:02:42.4991291Z def is_exception_branch(branch: str) -> bool: 2025-05-06T20:02:42.4991828Z """ 2025-05-06T20:02:42.4992460Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-05-06T20:02:42.4993218Z """ 2025-05-06T20:02:42.4993746Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-05-06T20:02:42.4994273Z 2025-05-06T20:02:42.4994281Z 2025-05-06T20:02:42.4994472Z def load_yaml(yaml_text: str) -> Any: 2025-05-06T20:02:42.4994949Z try: 2025-05-06T20:02:42.4995344Z data = yaml.safe_load(yaml_text) 2025-05-06T20:02:42.4995844Z return data 2025-05-06T20:02:42.4996250Z except yaml.YAMLError: 2025-05-06T20:02:42.4996719Z log.exception("Error loading YAML") 2025-05-06T20:02:42.4997227Z raise 2025-05-06T20:02:42.4997443Z 2025-05-06T20:02:42.4997450Z 2025-05-06T20:02:42.4997862Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-05-06T20:02:42.4998597Z """ 2025-05-06T20:02:42.4999199Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-05-06T20:02:42.4999785Z 2025-05-06T20:02:42.5000124Z If the issue body contains "---" then the text above that is the settings 2025-05-06T20:02:42.5001057Z and the text below is the list of opted in users. 2025-05-06T20:02:42.5001467Z 2025-05-06T20:02:42.5001838Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-05-06T20:02:42.5002518Z """ 2025-05-06T20:02:42.5002986Z rollout_state_parts = rollout_state.split("---") 2025-05-06T20:02:42.5003574Z if len(rollout_state_parts) >= 2: 2025-05-06T20:02:42.5004164Z return rollout_state_parts[0], rollout_state_parts[1] 2025-05-06T20:02:42.5004732Z else: 2025-05-06T20:02:42.5005111Z return "", rollout_state 2025-05-06T20:02:42.5005410Z 2025-05-06T20:02:42.5005417Z 2025-05-06T20:02:42.5005621Z class UserOptins(dict[str, list[str]]): 2025-05-06T20:02:42.5006109Z """ 2025-05-06T20:02:42.5006614Z Dictionary of users with a list of features they have opted into 2025-05-06T20:02:42.5007251Z """ 2025-05-06T20:02:42.5007447Z 2025-05-06T20:02:42.5007454Z 2025-05-06T20:02:42.5007782Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-05-06T20:02:42.5008423Z """ 2025-05-06T20:02:42.5009340Z 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:02:42.5010018Z 2025-05-06T20:02:42.5010813Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-05-06T20:02:42.5011804Z - Example line: "@User1,lf,split_build" 2025-05-06T20:02:42.5012473Z - A "#" prefix indicates the user is opted out of all experiments 2025-05-06T20:02:42.5012940Z 2025-05-06T20:02:42.5012947Z 2025-05-06T20:02:42.5013099Z """ 2025-05-06T20:02:42.5013472Z optins = UserOptins() 2025-05-06T20:02:42.5013952Z for user in user_optin_text.split("\n"): 2025-05-06T20:02:42.5014493Z user = user.strip("\r\n\t -") 2025-05-06T20:02:42.5015031Z if not user or not user.startswith("@"): 2025-05-06T20:02:42.5015575Z # Not a valid user. Skip 2025-05-06T20:02:42.5016050Z continue 2025-05-06T20:02:42.5016287Z 2025-05-06T20:02:42.5016456Z if user: 2025-05-06T20:02:42.5016887Z usr_name = user.split(",")[0].strip("@") 2025-05-06T20:02:42.5017708Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-05-06T20:02:42.5018215Z 2025-05-06T20:02:42.5018382Z return optins 2025-05-06T20:02:42.5018615Z 2025-05-06T20:02:42.5018623Z 2025-05-06T20:02:42.5018908Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-05-06T20:02:42.5019499Z """ 2025-05-06T20:02:42.5019894Z Check if the experiment name is valid. 2025-05-06T20:02:42.5020407Z A valid name: 2025-05-06T20:02:42.5021297Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-05-06T20:02:42.5022212Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-05-06T20:02:42.5022944Z - Cannot contain spaces 2025-05-06T20:02:42.5023406Z """ 2025-05-06T20:02:42.5023602Z 2025-05-06T20:02:42.5023857Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-05-06T20:02:42.5024565Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-05-06T20:02:42.5025001Z 2025-05-06T20:02:42.5025158Z if valid: 2025-05-06T20:02:42.5025538Z return True 2025-05-06T20:02:42.5025775Z 2025-05-06T20:02:42.5025935Z log.error( 2025-05-06T20:02:42.5027361Z 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:02:42.5028987Z ) 2025-05-06T20:02:42.5029335Z return False 2025-05-06T20:02:42.5029572Z 2025-05-06T20:02:42.5029578Z 2025-05-06T20:02:42.5029874Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-05-06T20:02:42.5030654Z """ 2025-05-06T20:02:42.5031244Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-05-06T20:02:42.5031945Z """ 2025-05-06T20:02:42.5032280Z try: 2025-05-06T20:02:42.5032660Z if settings_text: 2025-05-06T20:02:42.5033364Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-05-06T20:02:42.5034144Z # for easy reading 2025-05-06T20:02:42.5034904Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-05-06T20:02:42.5035774Z # the backtick character in shell commands. 2025-05-06T20:02:42.5036364Z backtick = chr(96) # backtick character 2025-05-06T20:02:42.5037007Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-05-06T20:02:42.5037659Z settings = load_yaml(settings_text) 2025-05-06T20:02:42.5038026Z 2025-05-06T20:02:42.5038417Z # For now we just load experiments. We can expand this if/when we add more settings 2025-05-06T20:02:42.5039162Z experiments = {} 2025-05-06T20:02:42.5039475Z 2025-05-06T20:02:42.5039833Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-05-06T20:02:42.5040955Z if not is_valid_experiment_name(exp_name): 2025-05-06T20:02:42.5042070Z # 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:02:42.5043091Z continue 2025-05-06T20:02:42.5043374Z 2025-05-06T20:02:42.5043554Z valid_settings = {} 2025-05-06T20:02:42.5044060Z for setting in exp_settings: 2025-05-06T20:02:42.5044629Z if setting not in Experiment._fields: 2025-05-06T20:02:42.5045171Z log.warning( 2025-05-06T20:02:42.5045857Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-05-06T20:02:42.5046562Z ) 2025-05-06T20:02:42.5046978Z else: 2025-05-06T20:02:42.5047482Z valid_settings[setting] = exp_settings[setting] 2025-05-06T20:02:42.5047904Z 2025-05-06T20:02:42.5048174Z experiments[exp_name] = Experiment(**valid_settings) 2025-05-06T20:02:42.5048940Z return Settings(experiments) 2025-05-06T20:02:42.5049294Z 2025-05-06T20:02:42.5049476Z except Exception: 2025-05-06T20:02:42.5049951Z log.exception("Failed to parse settings") 2025-05-06T20:02:42.5050329Z 2025-05-06T20:02:42.5050681Z return Settings() 2025-05-06T20:02:42.5050948Z 2025-05-06T20:02:42.5050954Z 2025-05-06T20:02:42.5051199Z def parse_settings(rollout_state: str) -> Settings: 2025-05-06T20:02:42.5051757Z """ 2025-05-06T20:02:42.5052165Z Parse settings, if any, from the rollout state. 2025-05-06T20:02:42.5052564Z 2025-05-06T20:02:42.5052905Z If the issue body contains "---" then the text above that is the settings 2025-05-06T20:02:42.5053639Z and the text below is the list of opted in users. 2025-05-06T20:02:42.5054284Z 2025-05-06T20:02:42.5054770Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-05-06T20:02:42.5055504Z """ 2025-05-06T20:02:42.5056050Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:02:42.5056795Z return parse_settings_from_text(settings_text) 2025-05-06T20:02:42.5057199Z 2025-05-06T20:02:42.5057206Z 2025-05-06T20:02:42.5057456Z def parse_users(rollout_state: str) -> UserOptins: 2025-05-06T20:02:42.5058003Z """ 2025-05-06T20:02:42.5058383Z Parse users from the rollout state. 2025-05-06T20:02:42.5058746Z 2025-05-06T20:02:42.5058906Z """ 2025-05-06T20:02:42.5059434Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:02:42.5060169Z return parse_user_opt_in_from_text(users_text) 2025-05-06T20:02:42.5060832Z 2025-05-06T20:02:42.5060840Z 2025-05-06T20:02:42.5061262Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:02:42.5062016Z """ 2025-05-06T20:02:42.5062419Z Check if a user is opted into an experiment 2025-05-06T20:02:42.5062946Z """ 2025-05-06T20:02:42.5063379Z return experiment_name in user_optins.get(user, []) 2025-05-06T20:02:42.5063795Z 2025-05-06T20:02:42.5063808Z 2025-05-06T20:02:42.5064224Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:02:42.5064955Z """ 2025-05-06T20:02:42.5065405Z Check if a user explicitly opted out of an experiment 2025-05-06T20:02:42.5065961Z """ 2025-05-06T20:02:42.5066453Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-05-06T20:02:42.5067115Z experiment_optout = "-" + experiment_name 2025-05-06T20:02:42.5067734Z if experiment_optout not in user_optins.get(user, []): 2025-05-06T20:02:42.5068323Z return False 2025-05-06T20:02:42.5068571Z 2025-05-06T20:02:42.5068841Z if is_user_opted_in(user, user_optins, experiment_name): 2025-05-06T20:02:42.5069429Z log.warning( 2025-05-06T20:02:42.5070387Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-05-06T20:02:42.5071485Z ) 2025-05-06T20:02:42.5071695Z 2025-05-06T20:02:42.5071863Z return True 2025-05-06T20:02:42.5072089Z 2025-05-06T20:02:42.5072096Z 2025-05-06T20:02:42.5072271Z def get_runner_prefix( 2025-05-06T20:02:42.5072698Z rollout_state: str, 2025-05-06T20:02:42.5073144Z workflow_requestors: Iterable[str], 2025-05-06T20:02:42.5073660Z branch: str, 2025-05-06T20:02:42.5074133Z eligible_experiments: frozenset[str] = frozenset(), 2025-05-06T20:02:42.5074727Z is_canary: bool = False, 2025-05-06T20:02:42.5075170Z ) -> str: 2025-05-06T20:02:42.5075577Z settings = parse_settings(rollout_state) 2025-05-06T20:02:42.5076139Z user_optins = parse_users(rollout_state) 2025-05-06T20:02:42.5076508Z 2025-05-06T20:02:42.5076677Z fleet_prefix = "" 2025-05-06T20:02:42.5077097Z prefixes = [] 2025-05-06T20:02:42.5077723Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-05-06T20:02:42.5078675Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-05-06T20:02:42.5079500Z log.info( 2025-05-06T20:02:42.5080188Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-05-06T20:02:42.5081118Z ) 2025-05-06T20:02:42.5081490Z continue 2025-05-06T20:02:42.5081737Z 2025-05-06T20:02:42.5081928Z if eligible_experiments: 2025-05-06T20:02:42.5082479Z if experiment_name not in eligible_experiments: 2025-05-06T20:02:42.5083115Z exp_list = ", ".join(eligible_experiments) 2025-05-06T20:02:42.5083663Z log.info( 2025-05-06T20:02:42.5084439Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-05-06T20:02:42.5085263Z ) 2025-05-06T20:02:42.5085659Z continue 2025-05-06T20:02:42.5086122Z elif not experiment_settings.default: 2025-05-06T20:02:42.5086654Z log.info( 2025-05-06T20:02:42.5087305Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-05-06T20:02:42.5088030Z ) 2025-05-06T20:02:42.5088411Z continue 2025-05-06T20:02:42.5088658Z 2025-05-06T20:02:42.5088933Z # Is any workflow_requestor opted out to this experiment? 2025-05-06T20:02:42.5089547Z opted_out_users = [ 2025-05-06T20:02:42.5089985Z requestor 2025-05-06T20:02:42.5090583Z for requestor in workflow_requestors 2025-05-06T20:02:42.5091264Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-05-06T20:02:42.5091889Z ] 2025-05-06T20:02:42.5092090Z 2025-05-06T20:02:42.5092267Z if opted_out_users: 2025-05-06T20:02:42.5092702Z log.info( 2025-05-06T20:02:42.5093317Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-05-06T20:02:42.5094011Z ) 2025-05-06T20:02:42.5094380Z continue 2025-05-06T20:02:42.5094625Z 2025-05-06T20:02:42.5094897Z # Is any workflow_requestor opted in to this experiment? 2025-05-06T20:02:42.5095503Z opted_in_users = [ 2025-05-06T20:02:42.5095940Z requestor 2025-05-06T20:02:42.5096383Z for requestor in workflow_requestors 2025-05-06T20:02:42.5097035Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-05-06T20:02:42.5097638Z ] 2025-05-06T20:02:42.5097848Z 2025-05-06T20:02:42.5098014Z enabled = False 2025-05-06T20:02:42.5098441Z if opted_in_users: 2025-05-06T20:02:42.5098888Z log.info( 2025-05-06T20:02:42.5099485Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-05-06T20:02:42.5100161Z ) 2025-05-06T20:02:42.5100762Z enabled = True 2025-05-06T20:02:42.5101228Z 2025-05-06T20:02:42.5101452Z elif experiment_settings.rollout_perc: 2025-05-06T20:02:42.5102290Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-05-06T20:02:42.5103253Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-05-06T20:02:42.5103904Z log.info( 2025-05-06T20:02:42.5104755Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-05-06T20:02:42.5105699Z ) 2025-05-06T20:02:42.5106099Z enabled = True 2025-05-06T20:02:42.5106395Z 2025-05-06T20:02:42.5106555Z if enabled: 2025-05-06T20:02:42.5106985Z label = experiment_name 2025-05-06T20:02:42.5107521Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-05-06T20:02:42.5108334Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-05-06T20:02:42.5109213Z # - If it's enabled, then we always list it's prefix first 2025-05-06T20:02:42.5110095Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-05-06T20:02:42.5110967Z if is_canary: 2025-05-06T20:02:42.5111454Z label += CANARY_FLEET_SUFFIX 2025-05-06T20:02:42.5111999Z fleet_prefix = label 2025-05-06T20:02:42.5112476Z else: 2025-05-06T20:02:42.5112898Z prefixes.append(label) 2025-05-06T20:02:42.5113239Z 2025-05-06T20:02:42.5113420Z if len(prefixes) > 1: 2025-05-06T20:02:42.5113854Z log.error( 2025-05-06T20:02:42.5114872Z 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:02:42.5115968Z ) 2025-05-06T20:02:42.5116345Z prefixes = prefixes[:1] 2025-05-06T20:02:42.5116644Z 2025-05-06T20:02:42.5116845Z # Fleet always comes first 2025-05-06T20:02:42.5117302Z if fleet_prefix: 2025-05-06T20:02:42.5117734Z prefixes.insert(0, fleet_prefix) 2025-05-06T20:02:42.5118090Z 2025-05-06T20:02:42.5118335Z return ".".join(prefixes) + "." if prefixes else "" 2025-05-06T20:02:42.5118739Z 2025-05-06T20:02:42.5118746Z 2025-05-06T20:02:42.5119181Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-05-06T20:02:42.5119940Z """ 2025-05-06T20:02:42.5120692Z Gets the first comment of the issue, which contains the desired rollout state. 2025-05-06T20:02:42.5121284Z 2025-05-06T20:02:42.5121680Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-05-06T20:02:42.5122375Z """ 2025-05-06T20:02:42.5122748Z gh = get_gh_client(github_token) 2025-05-06T20:02:42.5123285Z issue = get_issue(gh, repo, issue_num) 2025-05-06T20:02:42.5123905Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-05-06T20:02:42.5124351Z 2025-05-06T20:02:42.5124358Z 2025-05-06T20:02:42.5124751Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-05-06T20:02:42.5125500Z for _ in range(num_retries): 2025-05-06T20:02:42.5125965Z try: 2025-05-06T20:02:42.5126380Z req = Request(url=url, headers=headers) 2025-05-06T20:02:42.5127024Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-05-06T20:02:42.5127658Z return json.loads(content) 2025-05-06T20:02:42.5128174Z except Exception as e: 2025-05-06T20:02:42.5128701Z log.warning(f"Could not download {url}: {e}") 2025-05-06T20:02:42.5129098Z 2025-05-06T20:02:42.5129478Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-05-06T20:02:42.5130172Z return {} 2025-05-06T20:02:42.5130397Z 2025-05-06T20:02:42.5130404Z 2025-05-06T20:02:42.5130790Z @cache 2025-05-06T20:02:42.5131428Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-05-06T20:02:42.5132379Z """ 2025-05-06T20:02:42.5132799Z Dynamically get PR information 2025-05-06T20:02:42.5133279Z """ 2025-05-06T20:02:42.5133767Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-05-06T20:02:42.5134397Z headers = { 2025-05-06T20:02:42.5134855Z "Accept": "application/vnd.github.v3+json", 2025-05-06T20:02:42.5135445Z "Authorization": f"token {github_token}", 2025-05-06T20:02:42.5135971Z } 2025-05-06T20:02:42.5136385Z json_response: dict[str, Any] = download_json( 2025-05-06T20:02:42.5136978Z url=f"{github_api}/issues/{pr_number}", 2025-05-06T20:02:42.5137512Z headers=headers, 2025-05-06T20:02:42.5137929Z ) 2025-05-06T20:02:42.5138124Z 2025-05-06T20:02:42.5138305Z if not json_response: 2025-05-06T20:02:42.5138859Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-05-06T20:02:42.5139470Z return {} 2025-05-06T20:02:42.5139699Z 2025-05-06T20:02:42.5139883Z return json_response 2025-05-06T20:02:42.5140157Z 2025-05-06T20:02:42.5140164Z 2025-05-06T20:02:42.5140807Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-05-06T20:02:42.5141768Z """ 2025-05-06T20:02:42.5142315Z Dynamically get the latest list of labels from the pull request 2025-05-06T20:02:42.5142956Z """ 2025-05-06T20:02:42.5143417Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-05-06T20:02:42.5144016Z return { 2025-05-06T20:02:42.5144575Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-05-06T20:02:42.5145267Z } 2025-05-06T20:02:42.5145462Z 2025-05-06T20:02:42.5145469Z 2025-05-06T20:02:42.5145633Z def main() -> None: 2025-05-06T20:02:42.5146038Z args = parse_args() 2025-05-06T20:02:42.5146300Z 2025-05-06T20:02:42.5146516Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-05-06T20:02:42.5146891Z 2025-05-06T20:02:42.5147080Z # Check if the PR is opt-out 2025-05-06T20:02:42.5147564Z if args.pr_number: 2025-05-06T20:02:42.5148198Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-05-06T20:02:42.5148958Z if OPT_OUT_LABEL in labels: 2025-05-06T20:02:42.5149438Z log.info( 2025-05-06T20:02:42.5150106Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-05-06T20:02:42.5151669Z ) 2025-05-06T20:02:42.5152225Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:02:42.5152888Z sys.exit() 2025-05-06T20:02:42.5153141Z 2025-05-06T20:02:42.5153298Z try: 2025-05-06T20:02:42.5153718Z rollout_state = get_rollout_state_from_issue( 2025-05-06T20:02:42.5154403Z args.github_token, args.github_issue_repo, args.github_issue 2025-05-06T20:02:42.5155030Z ) 2025-05-06T20:02:42.5155226Z 2025-05-06T20:02:42.5155423Z username = get_potential_pr_author( 2025-05-06T20:02:42.5155963Z args.github_token, 2025-05-06T20:02:42.5156421Z args.github_repo, 2025-05-06T20:02:42.5156893Z args.github_actor, 2025-05-06T20:02:42.5157361Z args.github_ref_type, 2025-05-06T20:02:42.5157851Z args.github_branch, 2025-05-06T20:02:42.5158306Z ) 2025-05-06T20:02:42.5158507Z 2025-05-06T20:02:42.5158789Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-05-06T20:02:42.5159239Z 2025-05-06T20:02:42.5159444Z runner_label_prefix = get_runner_prefix( 2025-05-06T20:02:42.5159976Z rollout_state, 2025-05-06T20:02:42.5160611Z (args.github_issue_owner, username), 2025-05-06T20:02:42.5161162Z args.github_branch, 2025-05-06T20:02:42.5161643Z args.eligible_experiments, 2025-05-06T20:02:42.5162137Z is_canary, 2025-05-06T20:02:42.5162532Z ) 2025-05-06T20:02:42.5162735Z 2025-05-06T20:02:42.5162926Z except Exception as e: 2025-05-06T20:02:42.5163356Z log.error( 2025-05-06T20:02:42.5164201Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-05-06T20:02:42.5164950Z ) 2025-05-06T20:02:42.5165154Z 2025-05-06T20:02:42.5165482Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:02:42.5165973Z 2025-05-06T20:02:42.5165980Z 2025-05-06T20:02:42.5166162Z if __name__ == "__main__": 2025-05-06T20:02:42.5166607Z main() 2025-05-06T20:02:42.5166814Z 2025-05-06T20:02:42.5260895Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-05-06T20:02:42.5261836Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-05-06T20:02:42.5293301Z shell: /usr/bin/bash -e {0} 2025-05-06T20:02:42.5293770Z env: 2025-05-06T20:02:42.5294431Z GITHUB_TOKEN: *** 2025-05-06T20:02:42.5294849Z ISSUE_NUMBER: 5132 2025-05-06T20:02:42.5295284Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:02:42.5295779Z ISSUE_OWNER: 2025-05-06T20:02:42.5296176Z CHECK_EXPERIMENTS: 2025-05-06T20:02:42.5296615Z PR_NUMBER: 2025-05-06T20:02:42.5296992Z ##[endgroup] 2025-05-06T20:02:42.9113161Z Defaulting to user installation because normal site-packages is not writeable 2025-05-06T20:02:43.2263713Z Collecting urllib3==1.26.18 2025-05-06T20:02:43.2649461Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-05-06T20:02:43.2876298Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.9 MB/s eta 0:00:00 2025-05-06T20:02:43.3092511Z Collecting PyGithub==2.3.0 2025-05-06T20:02:43.3173934Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-05-06T20:02:43.3623018Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-05-06T20:02:43.3659496Z 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:02:43.3710304Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-05-06T20:02:43.3727639Z 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:02:43.3742454Z 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:02:43.3996447Z Collecting Deprecated (from PyGithub==2.3.0) 2025-05-06T20:02:43.4041774Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-05-06T20:02:43.4283499Z 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:02:43.5385692Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-05-06T20:02:43.5424423Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-05-06T20:02:43.6483472Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-05-06T20:02:43.6525129Z 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:02:43.6724522Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-05-06T20:02:43.6775605Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-05-06T20:02:43.7052947Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-05-06T20:02:43.7121369Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 27.4 MB/s eta 0:00:00 2025-05-06T20:02:43.7158795Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-05-06T20:02:43.7225549Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 72.1 MB/s eta 0:00:00 2025-05-06T20:02:43.7294152Z 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:02:43.7427588Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 72.5 MB/s eta 0:00:00 2025-05-06T20:02:43.7472108Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-05-06T20:02:43.7729657Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-05-06T20:02:43.7893790Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 31.9 MB/s eta 0:00:00 2025-05-06T20:02:43.8014064Z 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:02:43.8054768Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 31.9 MB/s eta 0:00:00 2025-05-06T20:02:43.8097532Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-05-06T20:02:43.8141866Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 37.7 MB/s eta 0:00:00 2025-05-06T20:02:44.1095350Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-05-06T20:02:44.6542093Z 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:02:44.7509895Z ##[group]Run curr_branch="ciflow/trunk/147470" 2025-05-06T20:02:44.7510294Z curr_branch="ciflow/trunk/147470" 2025-05-06T20:02:44.7510741Z curr_ref_type="tag" 2025-05-06T20:02:44.7510995Z echo "Current branch is '$curr_branch'" 2025-05-06T20:02:44.7511259Z  2025-05-06T20:02:44.7511444Z python3 runner_determinator.py \ 2025-05-06T20:02:44.7511730Z  --github-token "$GITHUB_TOKEN" \ 2025-05-06T20:02:44.7511984Z  --github-issue "$ISSUE_NUMBER" \ 2025-05-06T20:02:44.7512242Z  --github-branch "$curr_branch" \ 2025-05-06T20:02:44.7512509Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-05-06T20:02:44.7512782Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-05-06T20:02:44.7513053Z  --github-ref-type "$curr_ref_type" \ 2025-05-06T20:02:44.7513316Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-05-06T20:02:44.7513645Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-05-06T20:02:44.7513930Z  --pr-number "${PR_NUMBER}" 2025-05-06T20:02:44.7545550Z shell: /usr/bin/bash -e {0} 2025-05-06T20:02:44.7545781Z env: 2025-05-06T20:02:44.7546335Z GITHUB_TOKEN: *** 2025-05-06T20:02:44.7546524Z ISSUE_NUMBER: 5132 2025-05-06T20:02:44.7546728Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:02:44.7546958Z ISSUE_OWNER: 2025-05-06T20:02:44.7547138Z CHECK_EXPERIMENTS: 2025-05-06T20:02:44.7547317Z PR_NUMBER: 2025-05-06T20:02:44.7547486Z ##[endgroup] 2025-05-06T20:02:44.7598061Z Current branch is 'ciflow/trunk/147470' 2025-05-06T20:02:46.6669159Z INFO : Based on rollout percentage of 15%, enabling experiment lf. 2025-05-06T20:02:46.6669922Z INFO : Based on rollout percentage of 100%, enabling experiment ephemeral. 2025-05-06T20:02:46.6670991Z INFO : Setting output: label-type='lf.ephemeral.' 2025-05-06T20:02:46.7024547Z Evaluate and set job outputs 2025-05-06T20:02:46.7031484Z Set output 'label-type' 2025-05-06T20:02:46.7033340Z Cleaning up orphan processes