2025-05-06T20:01:58.6546704Z Current runner version: '2.323.0' 2025-05-06T20:01:58.6570866Z ##[group]Operating System 2025-05-06T20:01:58.6571743Z Ubuntu 2025-05-06T20:01:58.6572228Z 24.04.2 2025-05-06T20:01:58.6572669Z LTS 2025-05-06T20:01:58.6573203Z ##[endgroup] 2025-05-06T20:01:58.6573683Z ##[group]Runner Image 2025-05-06T20:01:58.6574304Z Image: ubuntu-24.04 2025-05-06T20:01:58.6574821Z Version: 20250427.1.0 2025-05-06T20:01:58.6576220Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250427.1/images/ubuntu/Ubuntu2404-Readme.md 2025-05-06T20:01:58.6577624Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250427.1 2025-05-06T20:01:58.6578524Z ##[endgroup] 2025-05-06T20:01:58.6579050Z ##[group]Runner Image Provisioner 2025-05-06T20:01:58.6579624Z 2.0.422.1 2025-05-06T20:01:58.6580122Z ##[endgroup] 2025-05-06T20:01:58.6581169Z ##[group]GITHUB_TOKEN Permissions 2025-05-06T20:01:58.6583197Z Contents: read 2025-05-06T20:01:58.6583809Z Metadata: read 2025-05-06T20:01:58.6584439Z Packages: read 2025-05-06T20:01:58.6585069Z ##[endgroup] 2025-05-06T20:01:58.6587336Z Secret source: Actions 2025-05-06T20:01:58.6588046Z Prepare workflow directory 2025-05-06T20:01:58.7147921Z Prepare all required actions 2025-05-06T20:01:58.7200276Z Complete job name: get-label-type / runner-determinator 2025-05-06T20:01:58.7826367Z ##[group]Run cat < runner_determinator.py 2025-05-06T20:01:58.7829510Z cat < runner_determinator.py 2025-05-06T20:01:58.7830622Z # flake8: noqa: G004 2025-05-06T20:01:58.7831587Z  2025-05-06T20:01:58.7832877Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-05-06T20:01:58.7834782Z # must be kept in sync. You can do it easily by running the following command: 2025-05-06T20:01:58.7837022Z # python .github/scripts/update_runner_determinator.py 2025-05-06T20:01:58.7838411Z  2025-05-06T20:01:58.7839105Z """ 2025-05-06T20:01:58.7840529Z This runner determinator is used to determine which set of runners to run a 2025-05-06T20:01:58.7842315Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-05-06T20:01:58.7844323Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-05-06T20:01:58.7846151Z of which runners should be used to run which job. 2025-05-06T20:01:58.7847333Z  2025-05-06T20:01:58.7848616Z The configuration has two parts, the settings and a list of opted-in users, 2025-05-06T20:01:58.7850312Z separated by a line containing "---". If the line is not present, the 2025-05-06T20:01:58.7852030Z settings are considered to be empty with only the second part, the user 2025-05-06T20:01:58.7854002Z list, defined. 2025-05-06T20:01:58.7854989Z  2025-05-06T20:01:58.7856486Z The first part is a YAML block that defines the rollout settings. This can be 2025-05-06T20:01:58.7858419Z used to define any settings that are needed to determine which runners to use. 2025-05-06T20:01:58.7860064Z It's fields are defined by the RolloutSettings class below. 2025-05-06T20:01:58.7861264Z  2025-05-06T20:01:58.7862479Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-05-06T20:01:58.7864194Z The user list is also a comma separated list of additional features or 2025-05-06T20:01:58.7865786Z experiments which the user could be opted in to. 2025-05-06T20:01:58.7867100Z  2025-05-06T20:01:58.7867912Z The user list has the following rules: 2025-05-06T20:01:58.7868817Z  2025-05-06T20:01:58.7870060Z - Users are GitHub usernames, which must start with the @ prefix 2025-05-06T20:01:58.7871648Z - Each user is also a comma-separated list of features/experiments to enable 2025-05-06T20:01:58.7873170Z - A "#" prefix opts the user out of all experiments 2025-05-06T20:01:58.7874662Z  2025-05-06T20:01:58.7875666Z Example config: 2025-05-06T20:01:58.7876845Z  # A list of experiments that can be opted into. 2025-05-06T20:01:58.7878142Z  # This defines the behavior they'll induce when opted into. 2025-05-06T20:01:58.7879441Z  # Expected syntax is: 2025-05-06T20:01:58.7880696Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-05-06T20:01:58.7882690Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-05-06T20:01:58.7884156Z  2025-05-06T20:01:58.7884864Z  experiments: 2025-05-06T20:01:58.7886119Z  lf: 2025-05-06T20:01:58.7886887Z  rollout_percent: 25 2025-05-06T20:01:58.7887882Z  all_branches: false 2025-05-06T20:01:58.7888835Z  default: true 2025-05-06T20:01:58.7889723Z  --- 2025-05-06T20:01:58.7890575Z  2025-05-06T20:01:58.7891249Z  # Opt-ins: 2025-05-06T20:01:58.7892488Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-05-06T20:01:58.7894337Z  # and specifying experiments to enable in a comma-separated list. 2025-05-06T20:01:58.7896158Z  # To always opt out of an experiment, prefix it with a "-". 2025-05-06T20:01:58.7897410Z  # Experiments should be from the above list. 2025-05-06T20:01:58.7898646Z  2025-05-06T20:01:58.7899449Z  @User1,-lf,split_build 2025-05-06T20:01:58.7900308Z  @User2,lf 2025-05-06T20:01:58.7901190Z  @User3,split_build 2025-05-06T20:01:58.7902059Z """ 2025-05-06T20:01:58.7902771Z  2025-05-06T20:01:58.7903525Z import json 2025-05-06T20:01:58.7904347Z import logging 2025-05-06T20:01:58.7905207Z import os 2025-05-06T20:01:58.7906238Z import random 2025-05-06T20:01:58.7907063Z import re 2025-05-06T20:01:58.7907806Z import sys 2025-05-06T20:01:58.7908826Z from argparse import ArgumentParser 2025-05-06T20:01:58.7909882Z from collections.abc import Iterable 2025-05-06T20:01:58.7911274Z from functools import cache 2025-05-06T20:01:58.7912324Z from logging import LogRecord 2025-05-06T20:01:58.7913353Z from typing import Any, NamedTuple 2025-05-06T20:01:58.7914515Z from urllib.request import Request, urlopen 2025-05-06T20:01:58.7915871Z  2025-05-06T20:01:58.7916663Z import yaml 2025-05-06T20:01:58.7917493Z from github import Auth, Github 2025-05-06T20:01:58.7918696Z from github.Issue import Issue 2025-05-06T20:01:58.7919681Z  2025-05-06T20:01:58.7920313Z  2025-05-06T20:01:58.7921447Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-05-06T20:01:58.7922796Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-05-06T20:01:58.7924495Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-05-06T20:01:58.7926143Z  2025-05-06T20:01:58.7926965Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-05-06T20:01:58.7928147Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-05-06T20:01:58.7929340Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-05-06T20:01:58.7930467Z OPT_OUT_LABEL = "no-runner-experiments" 2025-05-06T20:01:58.7931482Z  2025-05-06T20:01:58.7932376Z SETTING_EXPERIMENTS = "experiments" 2025-05-06T20:01:58.7933345Z  2025-05-06T20:01:58.7934074Z LF_FLEET_EXPERIMENT = "lf" 2025-05-06T20:01:58.7935202Z CANARY_FLEET_SUFFIX = ".c" 2025-05-06T20:01:58.7936252Z  2025-05-06T20:01:58.7936933Z  2025-05-06T20:01:58.7937677Z class Experiment(NamedTuple): 2025-05-06T20:01:58.7938987Z  rollout_perc: float = ( 2025-05-06T20:01:58.7940270Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-05-06T20:01:58.7941767Z  ) 2025-05-06T20:01:58.7942561Z  all_branches: bool = ( 2025-05-06T20:01:58.7943797Z  False # If True, the experiment is also enabled on the exception branches 2025-05-06T20:01:58.7945437Z  ) 2025-05-06T20:01:58.7946188Z  default: bool = ( 2025-05-06T20:01:58.7947357Z  True # If True, the experiment is enabled by default for all queries 2025-05-06T20:01:58.7948823Z  ) 2025-05-06T20:01:58.7949498Z  2025-05-06T20:01:58.7950216Z  # Add more fields as needed 2025-05-06T20:01:58.7951265Z  2025-05-06T20:01:58.7951979Z  2025-05-06T20:01:58.7952661Z class Settings(NamedTuple): 2025-05-06T20:01:58.7953732Z  """ 2025-05-06T20:01:58.7954732Z  Settings for the experiments that can be opted into. 2025-05-06T20:01:58.7956085Z  """ 2025-05-06T20:01:58.7956965Z  2025-05-06T20:01:58.7957783Z  experiments: dict[str, Experiment] = {} 2025-05-06T20:01:58.7958808Z  2025-05-06T20:01:58.7959865Z  2025-05-06T20:01:58.7960786Z class ColorFormatter(logging.Formatter): 2025-05-06T20:01:58.7961983Z  """Color codes the log messages based on the log level""" 2025-05-06T20:01:58.7963243Z  2025-05-06T20:01:58.7963981Z  COLORS = { 2025-05-06T20:01:58.7964794Z  "WARNING": "\033[33m", # Yellow 2025-05-06T20:01:58.7966166Z  "ERROR": "\033[31m", # Red 2025-05-06T20:01:58.7967178Z  "CRITICAL": "\033[31m", # Red 2025-05-06T20:01:58.7968305Z  "INFO": "\033[0m", # Reset 2025-05-06T20:01:58.7969271Z  "DEBUG": "\033[0m", # Reset 2025-05-06T20:01:58.7970276Z  } 2025-05-06T20:01:58.7971111Z  2025-05-06T20:01:58.7971974Z  def format(self, record: LogRecord) -> str: 2025-05-06T20:01:58.7973457Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-05-06T20:01:58.7974919Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-05-06T20:01:58.7976550Z  return super().format(record) 2025-05-06T20:01:58.7977579Z  2025-05-06T20:01:58.7978288Z  2025-05-06T20:01:58.7979124Z handler = logging.StreamHandler() 2025-05-06T20:01:58.7980548Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-05-06T20:01:58.7981997Z  2025-05-06T20:01:58.7982897Z log = logging.getLogger(os.path.basename(__file__)) 2025-05-06T20:01:58.7984091Z log.addHandler(handler) 2025-05-06T20:01:58.7985079Z log.setLevel(logging.INFO) 2025-05-06T20:01:58.7986234Z  2025-05-06T20:01:58.7986980Z  2025-05-06T20:01:58.7987940Z def set_github_output(key: str, value: str) -> None: 2025-05-06T20:01:58.7989075Z  """ 2025-05-06T20:01:58.7990122Z  Defines outputs of the github action that invokes this script 2025-05-06T20:01:58.7991432Z  """ 2025-05-06T20:01:58.7992284Z  if not GITHUB_OUTPUT: 2025-05-06T20:01:58.7994325Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-05-06T20:01:58.7996805Z  log.warning( 2025-05-06T20:01:58.7998433Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-05-06T20:01:58.8000233Z  ) 2025-05-06T20:01:58.8001224Z  print(f"::set-output name={key}::{value}") 2025-05-06T20:01:58.8002319Z  return 2025-05-06T20:01:58.8003133Z  2025-05-06T20:01:58.8004216Z  with open(GITHUB_OUTPUT, "a") as f: 2025-05-06T20:01:58.8005589Z  log.info(f"Setting output: {key}='{value}'") 2025-05-06T20:01:58.8006729Z  f.write(f"{key}={value}\n") 2025-05-06T20:01:58.8007838Z  2025-05-06T20:01:58.8008540Z  2025-05-06T20:01:58.8009547Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-05-06T20:01:58.8010901Z  return frozenset( 2025-05-06T20:01:58.8012098Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-05-06T20:01:58.8013420Z  ) 2025-05-06T20:01:58.8014191Z  2025-05-06T20:01:58.8014932Z  2025-05-06T20:01:58.8015906Z def parse_args() -> Any: 2025-05-06T20:01:58.8017238Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-05-06T20:01:58.8019079Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-05-06T20:01:58.8020515Z  parser.add_argument( 2025-05-06T20:01:58.8021813Z  "--github-issue-repo", 2025-05-06T20:01:58.8022916Z  type=str, 2025-05-06T20:01:58.8023809Z  required=False, 2025-05-06T20:01:58.8025121Z  default="pytorch/test-infra", 2025-05-06T20:01:58.8026421Z  help="GitHub repo to get the issue", 2025-05-06T20:01:58.8027551Z  ) 2025-05-06T20:01:58.8028522Z  parser.add_argument( 2025-05-06T20:01:58.8029476Z  "--github-repo", 2025-05-06T20:01:58.8030306Z  type=str, 2025-05-06T20:01:58.8031308Z  required=True, 2025-05-06T20:01:58.8032335Z  help="GitHub repo where CI is running", 2025-05-06T20:01:58.8033305Z  ) 2025-05-06T20:01:58.8034197Z  parser.add_argument( 2025-05-06T20:01:58.8035687Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-05-06T20:01:58.8037084Z  ) 2025-05-06T20:01:58.8037828Z  parser.add_argument( 2025-05-06T20:01:58.8039156Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-05-06T20:01:58.8040539Z  ) 2025-05-06T20:01:58.8041351Z  parser.add_argument( 2025-05-06T20:01:58.8042693Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-05-06T20:01:58.8043933Z  ) 2025-05-06T20:01:58.8044848Z  parser.add_argument( 2025-05-06T20:01:58.8046591Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-05-06T20:01:58.8048201Z  ) 2025-05-06T20:01:58.8049073Z  parser.add_argument( 2025-05-06T20:01:58.8049954Z  "--github-ref-type", 2025-05-06T20:01:58.8051017Z  type=str, 2025-05-06T20:01:58.8051893Z  required=True, 2025-05-06T20:01:58.8052932Z  help="Current GitHub ref type, branch or tag", 2025-05-06T20:01:58.8054101Z  ) 2025-05-06T20:01:58.8054888Z  parser.add_argument( 2025-05-06T20:01:58.8056124Z  "--eligible-experiments", 2025-05-06T20:01:58.8057218Z  type=_str_comma_separated_to_set, 2025-05-06T20:01:58.8058346Z  required=False, 2025-05-06T20:01:58.8059209Z  default="", 2025-05-06T20:01:58.8060960Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-05-06T20:01:58.8062771Z  ) 2025-05-06T20:01:58.8063620Z  parser.add_argument( 2025-05-06T20:01:58.8064677Z  "--pr-number", 2025-05-06T20:01:58.8065769Z  type=str, 2025-05-06T20:01:58.8066576Z  required=False, 2025-05-06T20:01:58.8140767Z  default="", 2025-05-06T20:01:58.8142338Z  help="the optional PR number where this is run", 2025-05-06T20:01:58.8143293Z  ) 2025-05-06T20:01:58.8143886Z  2025-05-06T20:01:58.8144527Z  return parser.parse_args() 2025-05-06T20:01:58.8145574Z  2025-05-06T20:01:58.8146163Z  2025-05-06T20:01:58.8147185Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-05-06T20:01:58.8148511Z  auth = Auth.Token(github_token) 2025-05-06T20:01:58.8149412Z  return Github(auth=auth) 2025-05-06T20:01:58.8150185Z  2025-05-06T20:01:58.8150732Z  2025-05-06T20:01:58.8151851Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-05-06T20:01:58.8153227Z  repo = gh.get_repo(repo) 2025-05-06T20:01:58.8154117Z  return repo.get_issue(number=issue_num) 2025-05-06T20:01:58.8155000Z  2025-05-06T20:01:58.8156015Z  2025-05-06T20:01:58.8156618Z def get_potential_pr_author( 2025-05-06T20:01:58.8157768Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-05-06T20:01:58.8158965Z ) -> str: 2025-05-06T20:01:58.8160093Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-05-06T20:01:58.8161519Z  # Fetch the actual username from the original PR. The PR number is 2025-05-06T20:01:58.8162814Z  # embedded in the tag name: ciflow// 2025-05-06T20:01:58.8163775Z  2025-05-06T20:01:58.8164416Z  gh = get_gh_client(github_token) 2025-05-06T20:01:58.8165449Z  2025-05-06T20:01:58.8166258Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-05-06T20:01:58.8167311Z  split_tag = ref_name.split("/") 2025-05-06T20:01:58.8168182Z  if ( 2025-05-06T20:01:58.8168874Z  len(split_tag) == 3 2025-05-06T20:01:58.8169764Z  and split_tag[0] == "ciflow" 2025-05-06T20:01:58.8170657Z  and split_tag[2].isnumeric() 2025-05-06T20:01:58.8171523Z  ): 2025-05-06T20:01:58.8172234Z  pr_number = split_tag[2] 2025-05-06T20:01:58.8173067Z  try: 2025-05-06T20:01:58.8173834Z  repository = gh.get_repo(repo) 2025-05-06T20:01:58.8174912Z  pull = repository.get_pull(number=int(pr_number)) 2025-05-06T20:01:58.8176164Z  except Exception as e: 2025-05-06T20:01:58.8177068Z  raise Exception( # noqa: TRY002 2025-05-06T20:01:58.8178239Z  f"issue with pull request {pr_number} from repo {repository}" 2025-05-06T20:01:58.8179355Z  ) from e 2025-05-06T20:01:58.8180316Z  return pull.user.login # type: ignore[no-any-return] 2025-05-06T20:01:58.8181583Z  # In all other cases, return the original input username 2025-05-06T20:01:58.8182585Z  return username 2025-05-06T20:01:58.8183288Z  2025-05-06T20:01:58.8183845Z  2025-05-06T20:01:58.8184556Z def is_exception_branch(branch: str) -> bool: 2025-05-06T20:01:58.8185661Z  """ 2025-05-06T20:01:58.8186794Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-05-06T20:01:58.8188150Z  """ 2025-05-06T20:01:58.8189090Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-05-06T20:01:58.8190236Z  2025-05-06T20:01:58.8190788Z  2025-05-06T20:01:58.8191438Z def load_yaml(yaml_text: str) -> Any: 2025-05-06T20:01:58.8192276Z  try: 2025-05-06T20:01:58.8192948Z  data = yaml.safe_load(yaml_text) 2025-05-06T20:01:58.8193832Z  return data 2025-05-06T20:01:58.8194836Z  except yaml.YAMLError: 2025-05-06T20:01:58.8196447Z  log.exception("Error loading YAML") 2025-05-06T20:01:58.8197357Z  raise 2025-05-06T20:01:58.8198011Z  2025-05-06T20:01:58.8198556Z  2025-05-06T20:01:58.8199606Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-05-06T20:01:58.8200901Z  """ 2025-05-06T20:01:58.8201993Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-05-06T20:01:58.8203275Z  2025-05-06T20:01:58.8204179Z  If the issue body contains "---" then the text above that is the settings 2025-05-06T20:01:58.8205672Z  and the text below is the list of opted in users. 2025-05-06T20:01:58.8206619Z  2025-05-06T20:01:58.8207583Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-05-06T20:01:58.8208793Z  """ 2025-05-06T20:01:58.8209561Z  rollout_state_parts = rollout_state.split("---") 2025-05-06T20:01:58.8210605Z  if len(rollout_state_parts) >= 2: 2025-05-06T20:01:58.8211885Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-05-06T20:01:58.8212963Z  else: 2025-05-06T20:01:58.8213632Z  return "", rollout_state 2025-05-06T20:01:58.8214435Z  2025-05-06T20:01:58.8214974Z  2025-05-06T20:01:58.8215844Z class UserOptins(dict[str, list[str]]): 2025-05-06T20:01:58.8216743Z  """ 2025-05-06T20:01:58.8217661Z  Dictionary of users with a list of features they have opted into 2025-05-06T20:01:58.8218742Z  """ 2025-05-06T20:01:58.8219325Z  2025-05-06T20:01:58.8219889Z  2025-05-06T20:01:58.8220794Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-05-06T20:01:58.8221990Z  """ 2025-05-06T20:01:58.8223255Z  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:01:58.8224679Z  2025-05-06T20:01:58.8226269Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-05-06T20:01:58.8228023Z  - Example line: "@User1,lf,split_build" 2025-05-06T20:01:58.8229208Z  - A "#" prefix indicates the user is opted out of all experiments 2025-05-06T20:01:58.8230290Z  2025-05-06T20:01:58.8230837Z  2025-05-06T20:01:58.8231385Z  """ 2025-05-06T20:01:58.8232024Z  optins = UserOptins() 2025-05-06T20:01:58.8232927Z  for user in user_optin_text.split("\n"): 2025-05-06T20:01:58.8233879Z  user = user.strip("\r\n\t -") 2025-05-06T20:01:58.8234819Z  if not user or not user.startswith("@"): 2025-05-06T20:01:58.8236027Z  # Not a valid user. Skip 2025-05-06T20:01:58.8236888Z  continue 2025-05-06T20:01:58.8237588Z  2025-05-06T20:01:58.8238153Z  if user: 2025-05-06T20:01:58.8238984Z  usr_name = user.split(",")[0].strip("@") 2025-05-06T20:01:58.8240196Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-05-06T20:01:58.8241281Z  2025-05-06T20:01:58.8241846Z  return optins 2025-05-06T20:01:58.8242547Z  2025-05-06T20:01:58.8243097Z  2025-05-06T20:01:58.8243921Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-05-06T20:01:58.8244953Z  """ 2025-05-06T20:01:58.8245846Z  Check if the experiment name is valid. 2025-05-06T20:01:58.8246770Z  A valid name: 2025-05-06T20:01:58.8247915Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-05-06T20:01:58.8249772Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-05-06T20:01:58.8251026Z  - Cannot contain spaces 2025-05-06T20:01:58.8251811Z  """ 2025-05-06T20:01:58.8252433Z  2025-05-06T20:01:58.8253191Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-05-06T20:01:58.8254406Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-05-06T20:01:58.8255628Z  2025-05-06T20:01:58.8256224Z  if valid: 2025-05-06T20:01:58.8256887Z  return True 2025-05-06T20:01:58.8257569Z  2025-05-06T20:01:58.8258124Z  log.error( 2025-05-06T20:01:58.8260657Z  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:01:58.8263351Z  ) 2025-05-06T20:01:58.8263959Z  return False 2025-05-06T20:01:58.8264656Z  2025-05-06T20:01:58.8265197Z  2025-05-06T20:01:58.8266446Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-05-06T20:01:58.8267532Z  """ 2025-05-06T20:01:58.8268563Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-05-06T20:01:58.8269784Z  """ 2025-05-06T20:01:58.8270364Z  try: 2025-05-06T20:01:58.8271010Z  if settings_text: 2025-05-06T20:01:58.8272299Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-05-06T20:01:58.8273638Z  # for easy reading 2025-05-06T20:01:58.8275046Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-05-06T20:01:58.8276789Z  # the backtick character in shell commands. 2025-05-06T20:01:58.8277858Z  backtick = chr(96) # backtick character 2025-05-06T20:01:58.8279005Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-05-06T20:01:58.8280156Z  settings = load_yaml(settings_text) 2025-05-06T20:01:58.8281031Z  2025-05-06T20:01:58.8282028Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-05-06T20:01:58.8283297Z  experiments = {} 2025-05-06T20:01:58.8284074Z  2025-05-06T20:01:58.8285034Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-05-06T20:01:58.8286544Z  if not is_valid_experiment_name(exp_name): 2025-05-06T20:01:58.8288449Z  # 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:01:58.8290259Z  continue 2025-05-06T20:01:58.8291016Z  2025-05-06T20:01:58.8291627Z  valid_settings = {} 2025-05-06T20:01:58.8292513Z  for setting in exp_settings: 2025-05-06T20:01:58.8293510Z  if setting not in Experiment._fields: 2025-05-06T20:01:58.8294456Z  log.warning( 2025-05-06T20:01:58.8295889Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-05-06T20:01:58.8297135Z  ) 2025-05-06T20:01:58.8297881Z  else: 2025-05-06T20:01:58.8298796Z  valid_settings[setting] = exp_settings[setting] 2025-05-06T20:01:58.8299831Z  2025-05-06T20:01:58.8300679Z  experiments[exp_name] = Experiment(**valid_settings) 2025-05-06T20:01:58.8301980Z  return Settings(experiments) 2025-05-06T20:01:58.8302831Z  2025-05-06T20:01:58.8303426Z  except Exception: 2025-05-06T20:01:58.8304301Z  log.exception("Failed to parse settings") 2025-05-06T20:01:58.8305200Z  2025-05-06T20:01:58.8305978Z  return Settings() 2025-05-06T20:01:58.8306697Z  2025-05-06T20:01:58.8307246Z  2025-05-06T20:01:58.8308010Z def parse_settings(rollout_state: str) -> Settings: 2025-05-06T20:01:58.8308979Z  """ 2025-05-06T20:01:58.8309761Z  Parse settings, if any, from the rollout state. 2025-05-06T20:01:58.8310699Z  2025-05-06T20:01:58.8311585Z  If the issue body contains "---" then the text above that is the settings 2025-05-06T20:01:58.8312908Z  and the text below is the list of opted in users. 2025-05-06T20:01:58.8313844Z  2025-05-06T20:01:58.8314861Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-05-06T20:01:58.8316324Z  """ 2025-05-06T20:01:58.8317299Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:01:58.8318842Z  return parse_settings_from_text(settings_text) 2025-05-06T20:01:58.8319796Z  2025-05-06T20:01:58.8320341Z  2025-05-06T20:01:58.8321119Z def parse_users(rollout_state: str) -> UserOptins: 2025-05-06T20:01:58.8322106Z  """ 2025-05-06T20:01:58.8322782Z  Parse users from the rollout state. 2025-05-06T20:01:58.8323627Z  2025-05-06T20:01:58.8324179Z  """ 2025-05-06T20:01:58.8325119Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:01:58.8326626Z  return parse_user_opt_in_from_text(users_text) 2025-05-06T20:01:58.8327547Z  2025-05-06T20:01:58.8328110Z  2025-05-06T20:01:58.8329168Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:01:58.8330451Z  """ 2025-05-06T20:01:58.8331176Z  Check if a user is opted into an experiment 2025-05-06T20:01:58.8332092Z  """ 2025-05-06T20:01:58.8332902Z  return experiment_name in user_optins.get(user, []) 2025-05-06T20:01:58.8333866Z  2025-05-06T20:01:58.8334405Z  2025-05-06T20:01:58.8335674Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:01:58.8336978Z  """ 2025-05-06T20:01:58.8337780Z  Check if a user explicitly opted out of an experiment 2025-05-06T20:01:58.8338764Z  """ 2025-05-06T20:01:58.8339658Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-05-06T20:01:58.8340829Z  experiment_optout = "-" + experiment_name 2025-05-06T20:01:58.8341976Z  if experiment_optout not in user_optins.get(user, []): 2025-05-06T20:01:58.8342986Z  return False 2025-05-06T20:01:58.8343671Z  2025-05-06T20:01:58.8344460Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-05-06T20:01:58.8345676Z  log.warning( 2025-05-06T20:01:58.8347070Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-05-06T20:01:58.8348561Z  ) 2025-05-06T20:01:58.8349173Z  2025-05-06T20:01:58.8349734Z  return True 2025-05-06T20:01:58.8350537Z  2025-05-06T20:01:58.8351076Z  2025-05-06T20:01:58.8351674Z def get_runner_prefix( 2025-05-06T20:01:58.8352449Z  rollout_state: str, 2025-05-06T20:01:58.8353267Z  workflow_requestors: Iterable[str], 2025-05-06T20:01:58.8354127Z  branch: str, 2025-05-06T20:01:58.8355459Z  eligible_experiments: frozenset[str] = frozenset(), 2025-05-06T20:01:58.8356512Z  is_canary: bool = False, 2025-05-06T20:01:58.8357274Z ) -> str: 2025-05-06T20:01:58.8358027Z  settings = parse_settings(rollout_state) 2025-05-06T20:01:58.8359047Z  user_optins = parse_users(rollout_state) 2025-05-06T20:01:58.8359908Z  2025-05-06T20:01:58.8360482Z  fleet_prefix = "" 2025-05-06T20:01:58.8361235Z  prefixes = [] 2025-05-06T20:01:58.8362358Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-05-06T20:01:58.8363969Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-05-06T20:01:58.8365230Z  log.info( 2025-05-06T20:01:58.8366625Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-05-06T20:01:58.8367917Z  ) 2025-05-06T20:01:58.8368575Z  continue 2025-05-06T20:01:58.8369261Z  2025-05-06T20:01:58.8369871Z  if eligible_experiments: 2025-05-06T20:01:58.8371071Z  if experiment_name not in eligible_experiments: 2025-05-06T20:01:58.8372221Z  exp_list = ", ".join(eligible_experiments) 2025-05-06T20:01:58.8373146Z  log.info( 2025-05-06T20:01:58.8374534Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-05-06T20:01:58.8376176Z  ) 2025-05-06T20:01:58.8376858Z  continue 2025-05-06T20:01:58.8377724Z  elif not experiment_settings.default: 2025-05-06T20:01:58.8378616Z  log.info( 2025-05-06T20:01:58.8379764Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-05-06T20:01:58.8381039Z  ) 2025-05-06T20:01:58.8381702Z  continue 2025-05-06T20:01:58.8382384Z  2025-05-06T20:01:58.8383153Z  # Is any workflow_requestor opted out to this experiment? 2025-05-06T20:01:58.8384234Z  opted_out_users = [ 2025-05-06T20:01:58.8385034Z  requestor 2025-05-06T20:01:58.8386039Z  for requestor in workflow_requestors 2025-05-06T20:01:58.8387215Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-05-06T20:01:58.8388249Z  ] 2025-05-06T20:01:58.8388845Z  2025-05-06T20:01:58.8389394Z  if opted_out_users: 2025-05-06T20:01:58.8390137Z  log.info( 2025-05-06T20:01:58.8391190Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-05-06T20:01:58.8392385Z  ) 2025-05-06T20:01:58.8393060Z  continue 2025-05-06T20:01:58.8393735Z  2025-05-06T20:01:58.8394491Z  # Is any workflow_requestor opted in to this experiment? 2025-05-06T20:01:58.8395820Z  opted_in_users = [ 2025-05-06T20:01:58.8396708Z  requestor 2025-05-06T20:01:58.8397569Z  for requestor in workflow_requestors 2025-05-06T20:01:58.8398836Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-05-06T20:01:58.8399955Z  ] 2025-05-06T20:01:58.8400658Z  2025-05-06T20:01:58.8401253Z  enabled = False 2025-05-06T20:01:58.8402023Z  if opted_in_users: 2025-05-06T20:01:58.8402914Z  log.info( 2025-05-06T20:01:58.8404011Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-05-06T20:01:58.8405192Z  ) 2025-05-06T20:01:58.8406847Z  enabled = True 2025-05-06T20:01:58.8407627Z  2025-05-06T20:01:58.8408331Z  elif experiment_settings.rollout_perc: 2025-05-06T20:01:58.8409761Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-05-06T20:01:58.8411374Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-05-06T20:01:58.8412486Z  log.info( 2025-05-06T20:01:58.8414003Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-05-06T20:01:58.8415866Z  ) 2025-05-06T20:01:58.8416597Z  enabled = True 2025-05-06T20:01:58.8417383Z  2025-05-06T20:01:58.8417944Z  if enabled: 2025-05-06T20:01:58.8418709Z  label = experiment_name 2025-05-06T20:01:58.8419680Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-05-06T20:01:58.8421164Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-05-06T20:01:58.8422914Z  # - If it's enabled, then we always list it's prefix first 2025-05-06T20:01:58.8424276Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-05-06T20:01:58.8425588Z  if is_canary: 2025-05-06T20:01:58.8426462Z  label += CANARY_FLEET_SUFFIX 2025-05-06T20:01:58.8427403Z  fleet_prefix = label 2025-05-06T20:01:58.8428240Z  else: 2025-05-06T20:01:58.8429003Z  prefixes.append(label) 2025-05-06T20:01:58.8429840Z  2025-05-06T20:01:58.8430431Z  if len(prefixes) > 1: 2025-05-06T20:01:58.8431211Z  log.error( 2025-05-06T20:01:58.8432993Z  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:01:58.8434932Z  ) 2025-05-06T20:01:58.8435813Z  prefixes = prefixes[:1] 2025-05-06T20:01:58.8436630Z  2025-05-06T20:01:58.8437225Z  # Fleet always comes first 2025-05-06T20:01:58.8438062Z  if fleet_prefix: 2025-05-06T20:01:58.8438875Z  prefixes.insert(0, fleet_prefix) 2025-05-06T20:01:58.8439716Z  2025-05-06T20:01:58.8440437Z  return ".".join(prefixes) + "." if prefixes else "" 2025-05-06T20:01:58.8441389Z  2025-05-06T20:01:58.8441944Z  2025-05-06T20:01:58.8442999Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-05-06T20:01:58.8444289Z  """ 2025-05-06T20:01:58.8445547Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-05-06T20:01:58.8446779Z  2025-05-06T20:01:58.8447738Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-05-06T20:01:58.8448956Z  """ 2025-05-06T20:01:58.8449650Z  gh = get_gh_client(github_token) 2025-05-06T20:01:58.8450585Z  issue = get_issue(gh, repo, issue_num) 2025-05-06T20:01:58.8451663Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-05-06T20:01:58.8452688Z  2025-05-06T20:01:58.8453234Z  2025-05-06T20:01:58.8454228Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-05-06T20:01:58.8455722Z  for _ in range(num_retries): 2025-05-06T20:01:58.8456574Z  try: 2025-05-06T20:01:58.8457324Z  req = Request(url=url, headers=headers) 2025-05-06T20:01:58.8458453Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-05-06T20:01:58.8459807Z  return json.loads(content) 2025-05-06T20:01:58.8460711Z  except Exception as e: 2025-05-06T20:01:58.8461673Z  log.warning(f"Could not download {url}: {e}") 2025-05-06T20:01:58.8462598Z  2025-05-06T20:01:58.8463578Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-05-06T20:01:58.8464808Z  return {} 2025-05-06T20:01:58.8465636Z  2025-05-06T20:01:58.8466186Z  2025-05-06T20:01:58.8466725Z @cache 2025-05-06T20:01:58.8467825Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-05-06T20:01:58.8469102Z  """ 2025-05-06T20:01:58.8469774Z  Dynamically get PR information 2025-05-06T20:01:58.8470600Z  """ 2025-05-06T20:01:58.8471475Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-05-06T20:01:58.8472554Z  headers = { 2025-05-06T20:01:58.8473349Z  "Accept": "application/vnd.github.v3+json", 2025-05-06T20:01:58.8474425Z  "Authorization": f"token {github_token}", 2025-05-06T20:01:58.8475514Z  } 2025-05-06T20:01:58.8476482Z  json_response: dict[str, Any] = download_json( 2025-05-06T20:01:58.8477529Z  url=f"{github_api}/issues/{pr_number}", 2025-05-06T20:01:58.8478461Z  headers=headers, 2025-05-06T20:01:58.8479203Z  ) 2025-05-06T20:01:58.8479789Z  2025-05-06T20:01:58.8480369Z  if not json_response: 2025-05-06T20:01:58.8481378Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-05-06T20:01:58.8482457Z  return {} 2025-05-06T20:01:58.8483138Z  2025-05-06T20:01:58.8483721Z  return json_response 2025-05-06T20:01:58.8484448Z  2025-05-06T20:01:58.8485008Z  2025-05-06T20:01:58.8486213Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-05-06T20:01:58.8487423Z  """ 2025-05-06T20:01:58.8488334Z  Dynamically get the latest list of labels from the pull request 2025-05-06T20:01:58.8489463Z  """ 2025-05-06T20:01:58.8490294Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-05-06T20:01:58.8491295Z  return { 2025-05-06T20:01:58.8492303Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-05-06T20:01:58.8493480Z  } 2025-05-06T20:01:58.8494075Z  2025-05-06T20:01:58.8494608Z  2025-05-06T20:01:58.8495173Z def main() -> None: 2025-05-06T20:01:58.8496146Z  args = parse_args() 2025-05-06T20:01:58.8496893Z  2025-05-06T20:01:58.8497585Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-05-06T20:01:58.8498466Z  2025-05-06T20:01:58.8499093Z  # Check if the PR is opt-out 2025-05-06T20:01:58.8499946Z  if args.pr_number: 2025-05-06T20:01:58.8501109Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-05-06T20:01:58.8502385Z  if OPT_OUT_LABEL in labels: 2025-05-06T20:01:58.8503233Z  log.info( 2025-05-06T20:01:58.8504457Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-05-06T20:01:58.8505931Z  ) 2025-05-06T20:01:58.8506920Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:01:58.8508091Z  sys.exit() 2025-05-06T20:01:58.8508822Z  2025-05-06T20:01:58.8509374Z  try: 2025-05-06T20:01:58.8510124Z  rollout_state = get_rollout_state_from_issue( 2025-05-06T20:01:58.8511367Z  args.github_token, args.github_issue_repo, args.github_issue 2025-05-06T20:01:58.8512660Z  ) 2025-05-06T20:01:58.8513269Z  2025-05-06T20:01:58.8513932Z  username = get_potential_pr_author( 2025-05-06T20:01:58.8514834Z  args.github_token, 2025-05-06T20:01:58.8515882Z  args.github_repo, 2025-05-06T20:01:58.8516699Z  args.github_actor, 2025-05-06T20:01:58.8517561Z  args.github_ref_type, 2025-05-06T20:01:58.8518416Z  args.github_branch, 2025-05-06T20:01:58.8519212Z  ) 2025-05-06T20:01:58.8519806Z  2025-05-06T20:01:58.8520599Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-05-06T20:01:58.8521695Z  2025-05-06T20:01:58.8522383Z  runner_label_prefix = get_runner_prefix( 2025-05-06T20:01:58.8523303Z  rollout_state, 2025-05-06T20:01:58.8524143Z  (args.github_issue_owner, username), 2025-05-06T20:01:58.8525107Z  args.github_branch, 2025-05-06T20:01:58.8526187Z  args.eligible_experiments, 2025-05-06T20:01:58.8527047Z  is_canary, 2025-05-06T20:01:58.8528035Z  ) 2025-05-06T20:01:58.8528697Z  2025-05-06T20:01:58.8529312Z  except Exception as e: 2025-05-06T20:01:58.8530089Z  log.error( 2025-05-06T20:01:58.8531258Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-05-06T20:01:58.8532540Z  ) 2025-05-06T20:01:58.8533161Z  2025-05-06T20:01:58.8534032Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:01:58.8535143Z  2025-05-06T20:01:58.8535918Z  2025-05-06T20:01:58.8536521Z if __name__ == "__main__": 2025-05-06T20:01:58.8537282Z  main() 2025-05-06T20:01:58.8537915Z  2025-05-06T20:01:58.8538455Z EOF 2025-05-06T20:01:58.8539013Z  2025-05-06T20:01:58.8539632Z cat runner_determinator.py 2025-05-06T20:01:58.8817521Z shell: /usr/bin/bash -e {0} 2025-05-06T20:01:58.8818673Z env: 2025-05-06T20:01:58.8819700Z GITHUB_TOKEN: *** 2025-05-06T20:01:58.8820347Z ISSUE_NUMBER: 5132 2025-05-06T20:01:58.8821035Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:01:58.8821869Z ISSUE_OWNER: 2025-05-06T20:01:58.8822484Z CHECK_EXPERIMENTS: 2025-05-06T20:01:58.8823130Z PR_NUMBER: 2025-05-06T20:01:58.8823706Z ##[endgroup] 2025-05-06T20:01:58.9103597Z # flake8: noqa: G004 2025-05-06T20:01:58.9104141Z 2025-05-06T20:01:58.9104871Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-05-06T20:01:58.9106751Z # must be kept in sync. You can do it easily by running the following command: 2025-05-06T20:01:58.9108112Z # python .github/scripts/update_runner_determinator.py 2025-05-06T20:01:58.9108938Z 2025-05-06T20:01:58.9109208Z """ 2025-05-06T20:01:58.9110138Z This runner determinator is used to determine which set of runners to run a 2025-05-06T20:01:58.9111631Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-05-06T20:01:58.9112749Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-05-06T20:01:58.9113532Z of which runners should be used to run which job. 2025-05-06T20:01:58.9113912Z 2025-05-06T20:01:58.9114285Z The configuration has two parts, the settings and a list of opted-in users, 2025-05-06T20:01:58.9115129Z separated by a line containing "---". If the line is not present, the 2025-05-06T20:01:58.9116191Z settings are considered to be empty with only the second part, the user 2025-05-06T20:01:58.9116833Z list, defined. 2025-05-06T20:01:58.9117046Z 2025-05-06T20:01:58.9117397Z The first part is a YAML block that defines the rollout settings. This can be 2025-05-06T20:01:58.9118258Z used to define any settings that are needed to determine which runners to use. 2025-05-06T20:01:58.9119294Z It's fields are defined by the RolloutSettings class below. 2025-05-06T20:01:58.9119713Z 2025-05-06T20:01:58.9120073Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-05-06T20:01:58.9120902Z The user list is also a comma separated list of additional features or 2025-05-06T20:01:58.9121595Z experiments which the user could be opted in to. 2025-05-06T20:01:58.9121968Z 2025-05-06T20:01:58.9122156Z The user list has the following rules: 2025-05-06T20:01:58.9122488Z 2025-05-06T20:01:58.9122783Z - Users are GitHub usernames, which must start with the @ prefix 2025-05-06T20:01:58.9123603Z - Each user is also a comma-separated list of features/experiments to enable 2025-05-06T20:01:58.9124314Z - A "#" prefix opts the user out of all experiments 2025-05-06T20:01:58.9124684Z 2025-05-06T20:01:58.9124851Z Example config: 2025-05-06T20:01:58.9125565Z # A list of experiments that can be opted into. 2025-05-06T20:01:58.9126252Z # This defines the behavior they'll induce when opted into. 2025-05-06T20:01:58.9126833Z # Expected syntax is: 2025-05-06T20:01:58.9127433Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-05-06T20:01:58.9128505Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-05-06T20:01:58.9129139Z 2025-05-06T20:01:58.9129299Z experiments: 2025-05-06T20:01:58.9129667Z lf: 2025-05-06T20:01:58.9130294Z rollout_percent: 25 2025-05-06T20:01:58.9130733Z all_branches: false 2025-05-06T20:01:58.9131142Z default: true 2025-05-06T20:01:58.9131521Z --- 2025-05-06T20:01:58.9131705Z 2025-05-06T20:01:58.9131858Z # Opt-ins: 2025-05-06T20:01:58.9132407Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-05-06T20:01:58.9133203Z # and specifying experiments to enable in a comma-separated list. 2025-05-06T20:01:58.9133938Z # To always opt out of an experiment, prefix it with a "-". 2025-05-06T20:01:58.9134555Z # Experiments should be from the above list. 2025-05-06T20:01:58.9134908Z 2025-05-06T20:01:58.9135079Z @User1,-lf,split_build 2025-05-06T20:01:58.9135693Z @User2,lf 2025-05-06T20:01:58.9136046Z @User3,split_build 2025-05-06T20:01:58.9136424Z """ 2025-05-06T20:01:58.9136597Z 2025-05-06T20:01:58.9136749Z import json 2025-05-06T20:01:58.9137092Z import logging 2025-05-06T20:01:58.9137433Z import os 2025-05-06T20:01:58.9137768Z import random 2025-05-06T20:01:58.9138113Z import re 2025-05-06T20:01:58.9138438Z import sys 2025-05-06T20:01:58.9138803Z from argparse import ArgumentParser 2025-05-06T20:01:58.9139279Z from collections.abc import Iterable 2025-05-06T20:01:58.9139766Z from functools import cache 2025-05-06T20:01:58.9140197Z from logging import LogRecord 2025-05-06T20:01:58.9140652Z from typing import Any, NamedTuple 2025-05-06T20:01:58.9141143Z from urllib.request import Request, urlopen 2025-05-06T20:01:58.9141495Z 2025-05-06T20:01:58.9141647Z import yaml 2025-05-06T20:01:58.9141995Z from github import Auth, Github 2025-05-06T20:01:58.9142449Z from github.Issue import Issue 2025-05-06T20:01:58.9142726Z 2025-05-06T20:01:58.9142732Z 2025-05-06T20:01:58.9142948Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-05-06T20:01:58.9143583Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-05-06T20:01:58.9144397Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-05-06T20:01:58.9144914Z 2025-05-06T20:01:58.9145127Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-05-06T20:01:58.9145872Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-05-06T20:01:58.9146349Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-05-06T20:01:58.9146878Z OPT_OUT_LABEL = "no-runner-experiments" 2025-05-06T20:01:58.9147207Z 2025-05-06T20:01:58.9147397Z SETTING_EXPERIMENTS = "experiments" 2025-05-06T20:01:58.9147705Z 2025-05-06T20:01:58.9147878Z LF_FLEET_EXPERIMENT = "lf" 2025-05-06T20:01:58.9148516Z CANARY_FLEET_SUFFIX = ".c" 2025-05-06T20:01:58.9148778Z 2025-05-06T20:01:58.9148785Z 2025-05-06T20:01:58.9148961Z class Experiment(NamedTuple): 2025-05-06T20:01:58.9149413Z rollout_perc: float = ( 2025-05-06T20:01:58.9150021Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-05-06T20:01:58.9150673Z ) 2025-05-06T20:01:58.9151014Z all_branches: bool = ( 2025-05-06T20:01:58.9151591Z False # If True, the experiment is also enabled on the exception branches 2025-05-06T20:01:58.9152230Z ) 2025-05-06T20:01:58.9152559Z default: bool = ( 2025-05-06T20:01:58.9153090Z True # If True, the experiment is enabled by default for all queries 2025-05-06T20:01:58.9153677Z ) 2025-05-06T20:01:58.9153858Z 2025-05-06T20:01:58.9154029Z # Add more fields as needed 2025-05-06T20:01:58.9154311Z 2025-05-06T20:01:58.9154317Z 2025-05-06T20:01:58.9154496Z class Settings(NamedTuple): 2025-05-06T20:01:58.9154898Z """ 2025-05-06T20:01:58.9155530Z Settings for the experiments that can be opted into. 2025-05-06T20:01:58.9156080Z """ 2025-05-06T20:01:58.9156261Z 2025-05-06T20:01:58.9156468Z experiments: dict[str, Experiment] = {} 2025-05-06T20:01:58.9156808Z 2025-05-06T20:01:58.9156814Z 2025-05-06T20:01:58.9157161Z class ColorFormatter(logging.Formatter): 2025-05-06T20:01:58.9157761Z """Color codes the log messages based on the log level""" 2025-05-06T20:01:58.9158175Z 2025-05-06T20:01:58.9158331Z COLORS = { 2025-05-06T20:01:58.9158694Z "WARNING": "\033[33m", # Yellow 2025-05-06T20:01:58.9159165Z "ERROR": "\033[31m", # Red 2025-05-06T20:01:58.9159635Z "CRITICAL": "\033[31m", # Red 2025-05-06T20:01:58.9160101Z "INFO": "\033[0m", # Reset 2025-05-06T20:01:58.9160542Z "DEBUG": "\033[0m", # Reset 2025-05-06T20:01:58.9160988Z } 2025-05-06T20:01:58.9161169Z 2025-05-06T20:01:58.9161380Z def format(self, record: LogRecord) -> str: 2025-05-06T20:01:58.9162076Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-05-06T20:01:58.9162814Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-05-06T20:01:58.9163358Z return super().format(record) 2025-05-06T20:01:58.9163677Z 2025-05-06T20:01:58.9163683Z 2025-05-06T20:01:58.9163873Z handler = logging.StreamHandler() 2025-05-06T20:01:58.9164525Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-05-06T20:01:58.9165060Z 2025-05-06T20:01:58.9165483Z log = logging.getLogger(os.path.basename(__file__)) 2025-05-06T20:01:58.9166071Z log.addHandler(handler) 2025-05-06T20:01:58.9166485Z log.setLevel(logging.INFO) 2025-05-06T20:01:58.9166746Z 2025-05-06T20:01:58.9166757Z 2025-05-06T20:01:58.9166986Z def set_github_output(key: str, value: str) -> None: 2025-05-06T20:01:58.9167505Z """ 2025-05-06T20:01:58.9167973Z Defines outputs of the github action that invokes this script 2025-05-06T20:01:58.9168546Z """ 2025-05-06T20:01:58.9168884Z if not GITHUB_OUTPUT: 2025-05-06T20:01:58.9169902Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-05-06T20:01:58.9170965Z log.warning( 2025-05-06T20:01:58.9171769Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-05-06T20:01:58.9172638Z ) 2025-05-06T20:01:58.9182825Z print(f"::set-output name={key}::{value}") 2025-05-06T20:01:58.9183398Z return 2025-05-06T20:01:58.9183618Z 2025-05-06T20:01:58.9183806Z with open(GITHUB_OUTPUT, "a") as f: 2025-05-06T20:01:58.9184347Z log.info(f"Setting output: {key}='{value}'") 2025-05-06T20:01:58.9184874Z f.write(f"{key}={value}\n") 2025-05-06T20:01:58.9185176Z 2025-05-06T20:01:58.9185183Z 2025-05-06T20:01:58.9185654Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-05-06T20:01:58.9186239Z return frozenset( 2025-05-06T20:01:58.9187030Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-05-06T20:01:58.9187665Z ) 2025-05-06T20:01:58.9187851Z 2025-05-06T20:01:58.9187859Z 2025-05-06T20:01:58.9188029Z def parse_args() -> Any: 2025-05-06T20:01:58.9188547Z parser = ArgumentParser("Get dynamic rollout settings") 2025-05-06T20:01:58.9189358Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-05-06T20:01:58.9190097Z parser.add_argument( 2025-05-06T20:01:58.9190518Z "--github-issue-repo", 2025-05-06T20:01:58.9190960Z type=str, 2025-05-06T20:01:58.9191322Z required=False, 2025-05-06T20:01:58.9191745Z default="pytorch/test-infra", 2025-05-06T20:01:58.9192254Z help="GitHub repo to get the issue", 2025-05-06T20:01:58.9192728Z ) 2025-05-06T20:01:58.9193075Z parser.add_argument( 2025-05-06T20:01:58.9193480Z "--github-repo", 2025-05-06T20:01:58.9193869Z type=str, 2025-05-06T20:01:58.9194234Z required=True, 2025-05-06T20:01:58.9194657Z help="GitHub repo where CI is running", 2025-05-06T20:01:58.9195140Z ) 2025-05-06T20:01:58.9195727Z parser.add_argument( 2025-05-06T20:01:58.9196293Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-05-06T20:01:58.9197052Z ) 2025-05-06T20:01:58.9197395Z parser.add_argument( 2025-05-06T20:01:58.9197979Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-05-06T20:01:58.9198615Z ) 2025-05-06T20:01:58.9198946Z parser.add_argument( 2025-05-06T20:01:58.9199539Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-05-06T20:01:58.9200180Z ) 2025-05-06T20:01:58.9200515Z parser.add_argument( 2025-05-06T20:01:58.9201126Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-05-06T20:01:58.9201787Z ) 2025-05-06T20:01:58.9202112Z parser.add_argument( 2025-05-06T20:01:58.9202522Z "--github-ref-type", 2025-05-06T20:01:58.9202930Z type=str, 2025-05-06T20:01:58.9203293Z required=True, 2025-05-06T20:01:58.9203731Z help="Current GitHub ref type, branch or tag", 2025-05-06T20:01:58.9204227Z ) 2025-05-06T20:01:58.9204562Z parser.add_argument( 2025-05-06T20:01:58.9204983Z "--eligible-experiments", 2025-05-06T20:01:58.9205587Z type=_str_comma_separated_to_set, 2025-05-06T20:01:58.9206059Z required=False, 2025-05-06T20:01:58.9206480Z default="", 2025-05-06T20:01:58.9207269Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-05-06T20:01:58.9208146Z ) 2025-05-06T20:01:58.9208469Z parser.add_argument( 2025-05-06T20:01:58.9208871Z "--pr-number", 2025-05-06T20:01:58.9209243Z type=str, 2025-05-06T20:01:58.9209596Z required=False, 2025-05-06T20:01:58.9209971Z default="", 2025-05-06T20:01:58.9210399Z help="the optional PR number where this is run", 2025-05-06T20:01:58.9210928Z ) 2025-05-06T20:01:58.9211101Z 2025-05-06T20:01:58.9211275Z return parser.parse_args() 2025-05-06T20:01:58.9211559Z 2025-05-06T20:01:58.9211565Z 2025-05-06T20:01:58.9211947Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-05-06T20:01:58.9212656Z auth = Auth.Token(github_token) 2025-05-06T20:01:58.9213122Z return Github(auth=auth) 2025-05-06T20:01:58.9213393Z 2025-05-06T20:01:58.9213399Z 2025-05-06T20:01:58.9213834Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-05-06T20:01:58.9214578Z repo = gh.get_repo(repo) 2025-05-06T20:01:58.9215032Z return repo.get_issue(number=issue_num) 2025-05-06T20:01:58.9215475Z 2025-05-06T20:01:58.9215482Z 2025-05-06T20:01:58.9215656Z def get_potential_pr_author( 2025-05-06T20:01:58.9216258Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-05-06T20:01:58.9217032Z ) -> str: 2025-05-06T20:01:58.9217500Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-05-06T20:01:58.9218246Z # Fetch the actual username from the original PR. The PR number is 2025-05-06T20:01:58.9218944Z # embedded in the tag name: ciflow// 2025-05-06T20:01:58.9219337Z 2025-05-06T20:01:58.9219509Z gh = get_gh_client(github_token) 2025-05-06T20:01:58.9219816Z 2025-05-06T20:01:58.9220062Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-05-06T20:01:58.9220641Z split_tag = ref_name.split("/") 2025-05-06T20:01:58.9221101Z if ( 2025-05-06T20:01:58.9221448Z len(split_tag) == 3 2025-05-06T20:01:58.9221895Z and split_tag[0] == "ciflow" 2025-05-06T20:01:58.9222379Z and split_tag[2].isnumeric() 2025-05-06T20:01:58.9222829Z ): 2025-05-06T20:01:58.9223181Z pr_number = split_tag[2] 2025-05-06T20:01:58.9223629Z try: 2025-05-06T20:01:58.9224018Z repository = gh.get_repo(repo) 2025-05-06T20:01:58.9224596Z pull = repository.get_pull(number=int(pr_number)) 2025-05-06T20:01:58.9225154Z except Exception as e: 2025-05-06T20:01:58.9225738Z raise Exception( # noqa: TRY002 2025-05-06T20:01:58.9226494Z f"issue with pull request {pr_number} from repo {repository}" 2025-05-06T20:01:58.9227111Z ) from e 2025-05-06T20:01:58.9227608Z return pull.user.login # type: ignore[no-any-return] 2025-05-06T20:01:58.9228248Z # In all other cases, return the original input username 2025-05-06T20:01:58.9228796Z return username 2025-05-06T20:01:58.9229011Z 2025-05-06T20:01:58.9229018Z 2025-05-06T20:01:58.9229221Z def is_exception_branch(branch: str) -> bool: 2025-05-06T20:01:58.9229708Z """ 2025-05-06T20:01:58.9230301Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-05-06T20:01:58.9231016Z """ 2025-05-06T20:01:58.9231525Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-05-06T20:01:58.9232008Z 2025-05-06T20:01:58.9232015Z 2025-05-06T20:01:58.9232197Z def load_yaml(yaml_text: str) -> Any: 2025-05-06T20:01:58.9232644Z try: 2025-05-06T20:01:58.9232987Z data = yaml.safe_load(yaml_text) 2025-05-06T20:01:58.9233447Z return data 2025-05-06T20:01:58.9233821Z except yaml.YAMLError: 2025-05-06T20:01:58.9234259Z log.exception("Error loading YAML") 2025-05-06T20:01:58.9234732Z raise 2025-05-06T20:01:58.9234921Z 2025-05-06T20:01:58.9234928Z 2025-05-06T20:01:58.9235509Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-05-06T20:01:58.9236210Z """ 2025-05-06T20:01:58.9236778Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-05-06T20:01:58.9237342Z 2025-05-06T20:01:58.9237665Z If the issue body contains "---" then the text above that is the settings 2025-05-06T20:01:58.9238372Z and the text below is the list of opted in users. 2025-05-06T20:01:58.9238749Z 2025-05-06T20:01:58.9239104Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-05-06T20:01:58.9239751Z """ 2025-05-06T20:01:58.9240151Z rollout_state_parts = rollout_state.split("---") 2025-05-06T20:01:58.9240704Z if len(rollout_state_parts) >= 2: 2025-05-06T20:01:58.9241254Z return rollout_state_parts[0], rollout_state_parts[1] 2025-05-06T20:01:58.9241796Z else: 2025-05-06T20:01:58.9242137Z return "", rollout_state 2025-05-06T20:01:58.9242426Z 2025-05-06T20:01:58.9242433Z 2025-05-06T20:01:58.9242616Z class UserOptins(dict[str, list[str]]): 2025-05-06T20:01:58.9243082Z """ 2025-05-06T20:01:58.9243555Z Dictionary of users with a list of features they have opted into 2025-05-06T20:01:58.9244154Z """ 2025-05-06T20:01:58.9244325Z 2025-05-06T20:01:58.9244331Z 2025-05-06T20:01:58.9244646Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-05-06T20:01:58.9245507Z """ 2025-05-06T20:01:58.9246176Z 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:01:58.9246816Z 2025-05-06T20:01:58.9247402Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-05-06T20:01:58.9248374Z - Example line: "@User1,lf,split_build" 2025-05-06T20:01:58.9249003Z - A "#" prefix indicates the user is opted out of all experiments 2025-05-06T20:01:58.9249459Z 2025-05-06T20:01:58.9249465Z 2025-05-06T20:01:58.9249610Z """ 2025-05-06T20:01:58.9249957Z optins = UserOptins() 2025-05-06T20:01:58.9250397Z for user in user_optin_text.split("\n"): 2025-05-06T20:01:58.9250911Z user = user.strip("\r\n\t -") 2025-05-06T20:01:58.9251408Z if not user or not user.startswith("@"): 2025-05-06T20:01:58.9251918Z # Not a valid user. Skip 2025-05-06T20:01:58.9252364Z continue 2025-05-06T20:01:58.9252590Z 2025-05-06T20:01:58.9252739Z if user: 2025-05-06T20:01:58.9253126Z usr_name = user.split(",")[0].strip("@") 2025-05-06T20:01:58.9253907Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-05-06T20:01:58.9254376Z 2025-05-06T20:01:58.9254534Z return optins 2025-05-06T20:01:58.9254747Z 2025-05-06T20:01:58.9254754Z 2025-05-06T20:01:58.9255015Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-05-06T20:01:58.9255743Z """ 2025-05-06T20:01:58.9256096Z Check if the experiment name is valid. 2025-05-06T20:01:58.9256574Z A valid name: 2025-05-06T20:01:58.9257154Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-05-06T20:01:58.9258035Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-05-06T20:01:58.9258706Z - Cannot contain spaces 2025-05-06T20:01:58.9259119Z """ 2025-05-06T20:01:58.9259302Z 2025-05-06T20:01:58.9259549Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-05-06T20:01:58.9260195Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-05-06T20:01:58.9260602Z 2025-05-06T20:01:58.9260750Z if valid: 2025-05-06T20:01:58.9261091Z return True 2025-05-06T20:01:58.9261307Z 2025-05-06T20:01:58.9261454Z log.error( 2025-05-06T20:01:58.9262836Z 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:01:58.9264365Z ) 2025-05-06T20:01:58.9264690Z return False 2025-05-06T20:01:58.9264896Z 2025-05-06T20:01:58.9264903Z 2025-05-06T20:01:58.9265187Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-05-06T20:01:58.9265965Z """ 2025-05-06T20:01:58.9266511Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-05-06T20:01:58.9267177Z """ 2025-05-06T20:01:58.9267487Z try: 2025-05-06T20:01:58.9267818Z if settings_text: 2025-05-06T20:01:58.9268488Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-05-06T20:01:58.9269222Z # for easy reading 2025-05-06T20:01:58.9269955Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-05-06T20:01:58.9270783Z # the backtick character in shell commands. 2025-05-06T20:01:58.9271328Z backtick = chr(96) # backtick character 2025-05-06T20:01:58.9271939Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-05-06T20:01:58.9272543Z settings = load_yaml(settings_text) 2025-05-06T20:01:58.9272889Z 2025-05-06T20:01:58.9273260Z # For now we just load experiments. We can expand this if/when we add more settings 2025-05-06T20:01:58.9273953Z experiments = {} 2025-05-06T20:01:58.9274385Z 2025-05-06T20:01:58.9274720Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-05-06T20:01:58.9275637Z if not is_valid_experiment_name(exp_name): 2025-05-06T20:01:58.9276710Z # 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:01:58.9331649Z continue 2025-05-06T20:01:58.9332236Z 2025-05-06T20:01:58.9332594Z valid_settings = {} 2025-05-06T20:01:58.9333451Z for setting in exp_settings: 2025-05-06T20:01:58.9334379Z if setting not in Experiment._fields: 2025-05-06T20:01:58.9334943Z log.warning( 2025-05-06T20:01:58.9335914Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-05-06T20:01:58.9336598Z ) 2025-05-06T20:01:58.9337013Z else: 2025-05-06T20:01:58.9337488Z valid_settings[setting] = exp_settings[setting] 2025-05-06T20:01:58.9337894Z 2025-05-06T20:01:58.9338158Z experiments[exp_name] = Experiment(**valid_settings) 2025-05-06T20:01:58.9338962Z return Settings(experiments) 2025-05-06T20:01:58.9339321Z 2025-05-06T20:01:58.9339487Z except Exception: 2025-05-06T20:01:58.9339938Z log.exception("Failed to parse settings") 2025-05-06T20:01:58.9340302Z 2025-05-06T20:01:58.9340463Z return Settings() 2025-05-06T20:01:58.9340702Z 2025-05-06T20:01:58.9340708Z 2025-05-06T20:01:58.9340948Z def parse_settings(rollout_state: str) -> Settings: 2025-05-06T20:01:58.9341475Z """ 2025-05-06T20:01:58.9341878Z Parse settings, if any, from the rollout state. 2025-05-06T20:01:58.9342259Z 2025-05-06T20:01:58.9342590Z If the issue body contains "---" then the text above that is the settings 2025-05-06T20:01:58.9343307Z and the text below is the list of opted in users. 2025-05-06T20:01:58.9343692Z 2025-05-06T20:01:58.9344103Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-05-06T20:01:58.9344780Z """ 2025-05-06T20:01:58.9345502Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:01:58.9346225Z return parse_settings_from_text(settings_text) 2025-05-06T20:01:58.9346605Z 2025-05-06T20:01:58.9346611Z 2025-05-06T20:01:58.9346844Z def parse_users(rollout_state: str) -> UserOptins: 2025-05-06T20:01:58.9347381Z """ 2025-05-06T20:01:58.9347740Z Parse users from the rollout state. 2025-05-06T20:01:58.9348100Z 2025-05-06T20:01:58.9348249Z """ 2025-05-06T20:01:58.9348737Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-05-06T20:01:58.9349422Z return parse_user_opt_in_from_text(users_text) 2025-05-06T20:01:58.9349796Z 2025-05-06T20:01:58.9349802Z 2025-05-06T20:01:58.9350188Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:01:58.9350888Z """ 2025-05-06T20:01:58.9351268Z Check if a user is opted into an experiment 2025-05-06T20:01:58.9351761Z """ 2025-05-06T20:01:58.9352172Z return experiment_name in user_optins.get(user, []) 2025-05-06T20:01:58.9352563Z 2025-05-06T20:01:58.9352571Z 2025-05-06T20:01:58.9352964Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-05-06T20:01:58.9353657Z """ 2025-05-06T20:01:58.9354064Z Check if a user explicitly opted out of an experiment 2025-05-06T20:01:58.9354603Z """ 2025-05-06T20:01:58.9355055Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-05-06T20:01:58.9355963Z experiment_optout = "-" + experiment_name 2025-05-06T20:01:58.9356559Z if experiment_optout not in user_optins.get(user, []): 2025-05-06T20:01:58.9357116Z return False 2025-05-06T20:01:58.9357342Z 2025-05-06T20:01:58.9357597Z if is_user_opted_in(user, user_optins, experiment_name): 2025-05-06T20:01:58.9358311Z log.warning( 2025-05-06T20:01:58.9359052Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-05-06T20:01:58.9359864Z ) 2025-05-06T20:01:58.9360067Z 2025-05-06T20:01:58.9360215Z return True 2025-05-06T20:01:58.9360424Z 2025-05-06T20:01:58.9360431Z 2025-05-06T20:01:58.9360599Z def get_runner_prefix( 2025-05-06T20:01:58.9360994Z rollout_state: str, 2025-05-06T20:01:58.9361418Z workflow_requestors: Iterable[str], 2025-05-06T20:01:58.9361891Z branch: str, 2025-05-06T20:01:58.9362334Z eligible_experiments: frozenset[str] = frozenset(), 2025-05-06T20:01:58.9362882Z is_canary: bool = False, 2025-05-06T20:01:58.9363295Z ) -> str: 2025-05-06T20:01:58.9363667Z settings = parse_settings(rollout_state) 2025-05-06T20:01:58.9364343Z user_optins = parse_users(rollout_state) 2025-05-06T20:01:58.9364691Z 2025-05-06T20:01:58.9364862Z fleet_prefix = "" 2025-05-06T20:01:58.9365238Z prefixes = [] 2025-05-06T20:01:58.9366007Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-05-06T20:01:58.9366893Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-05-06T20:01:58.9367700Z log.info( 2025-05-06T20:01:58.9368331Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-05-06T20:01:58.9369046Z ) 2025-05-06T20:01:58.9369383Z continue 2025-05-06T20:01:58.9369612Z 2025-05-06T20:01:58.9369786Z if eligible_experiments: 2025-05-06T20:01:58.9370300Z if experiment_name not in eligible_experiments: 2025-05-06T20:01:58.9370885Z exp_list = ", ".join(eligible_experiments) 2025-05-06T20:01:58.9371408Z log.info( 2025-05-06T20:01:58.9372130Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-05-06T20:01:58.9372916Z ) 2025-05-06T20:01:58.9373273Z continue 2025-05-06T20:01:58.9373703Z elif not experiment_settings.default: 2025-05-06T20:01:58.9374200Z log.info( 2025-05-06T20:01:58.9374807Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-05-06T20:01:58.9375824Z ) 2025-05-06T20:01:58.9376178Z continue 2025-05-06T20:01:58.9376403Z 2025-05-06T20:01:58.9376669Z # Is any workflow_requestor opted out to this experiment? 2025-05-06T20:01:58.9377242Z opted_out_users = [ 2025-05-06T20:01:58.9377656Z requestor 2025-05-06T20:01:58.9378065Z for requestor in workflow_requestors 2025-05-06T20:01:58.9378724Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-05-06T20:01:58.9379317Z ] 2025-05-06T20:01:58.9379497Z 2025-05-06T20:01:58.9379659Z if opted_out_users: 2025-05-06T20:01:58.9380082Z log.info( 2025-05-06T20:01:58.9380641Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-05-06T20:01:58.9381295Z ) 2025-05-06T20:01:58.9381632Z continue 2025-05-06T20:01:58.9381861Z 2025-05-06T20:01:58.9382112Z # Is any workflow_requestor opted in to this experiment? 2025-05-06T20:01:58.9382684Z opted_in_users = [ 2025-05-06T20:01:58.9383088Z requestor 2025-05-06T20:01:58.9383500Z for requestor in workflow_requestors 2025-05-06T20:01:58.9384106Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-05-06T20:01:58.9384676Z ] 2025-05-06T20:01:58.9384861Z 2025-05-06T20:01:58.9385016Z enabled = False 2025-05-06T20:01:58.9385594Z if opted_in_users: 2025-05-06T20:01:58.9386001Z log.info( 2025-05-06T20:01:58.9386553Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-05-06T20:01:58.9387349Z ) 2025-05-06T20:01:58.9387699Z enabled = True 2025-05-06T20:01:58.9387956Z 2025-05-06T20:01:58.9388165Z elif experiment_settings.rollout_perc: 2025-05-06T20:01:58.9388938Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-05-06T20:01:58.9389809Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-05-06T20:01:58.9390405Z log.info( 2025-05-06T20:01:58.9391217Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-05-06T20:01:58.9392083Z ) 2025-05-06T20:01:58.9392446Z enabled = True 2025-05-06T20:01:58.9392715Z 2025-05-06T20:01:58.9392867Z if enabled: 2025-05-06T20:01:58.9393242Z label = experiment_name 2025-05-06T20:01:58.9393739Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-05-06T20:01:58.9394493Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-05-06T20:01:58.9395478Z # - If it's enabled, then we always list it's prefix first 2025-05-06T20:01:58.9396367Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-05-06T20:01:58.9396999Z if is_canary: 2025-05-06T20:01:58.9397455Z label += CANARY_FLEET_SUFFIX 2025-05-06T20:01:58.9397955Z fleet_prefix = label 2025-05-06T20:01:58.9398410Z else: 2025-05-06T20:01:58.9398796Z prefixes.append(label) 2025-05-06T20:01:58.9399121Z 2025-05-06T20:01:58.9399287Z if len(prefixes) > 1: 2025-05-06T20:01:58.9399683Z log.error( 2025-05-06T20:01:58.9400643Z 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:01:58.9401710Z ) 2025-05-06T20:01:58.9402063Z prefixes = prefixes[:1] 2025-05-06T20:01:58.9402338Z 2025-05-06T20:01:58.9402515Z # Fleet always comes first 2025-05-06T20:01:58.9402933Z if fleet_prefix: 2025-05-06T20:01:58.9403338Z prefixes.insert(0, fleet_prefix) 2025-05-06T20:01:58.9403662Z 2025-05-06T20:01:58.9403903Z return ".".join(prefixes) + "." if prefixes else "" 2025-05-06T20:01:58.9404292Z 2025-05-06T20:01:58.9404299Z 2025-05-06T20:01:58.9404704Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-05-06T20:01:58.9405601Z """ 2025-05-06T20:01:58.9406139Z Gets the first comment of the issue, which contains the desired rollout state. 2025-05-06T20:01:58.9406664Z 2025-05-06T20:01:58.9407028Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-05-06T20:01:58.9407682Z """ 2025-05-06T20:01:58.9408028Z gh = get_gh_client(github_token) 2025-05-06T20:01:58.9408520Z issue = get_issue(gh, repo, issue_num) 2025-05-06T20:01:58.9409109Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-05-06T20:01:58.9409520Z 2025-05-06T20:01:58.9409527Z 2025-05-06T20:01:58.9409902Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-05-06T20:01:58.9410604Z for _ in range(num_retries): 2025-05-06T20:01:58.9411047Z try: 2025-05-06T20:01:58.9411424Z req = Request(url=url, headers=headers) 2025-05-06T20:01:58.9412039Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-05-06T20:01:58.9412635Z return json.loads(content) 2025-05-06T20:01:58.9413126Z except Exception as e: 2025-05-06T20:01:58.9413624Z log.warning(f"Could not download {url}: {e}") 2025-05-06T20:01:58.9413998Z 2025-05-06T20:01:58.9414350Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-05-06T20:01:58.9415003Z return {} 2025-05-06T20:01:58.9415204Z 2025-05-06T20:01:58.9415210Z 2025-05-06T20:01:58.9415588Z @cache 2025-05-06T20:01:58.9416376Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-05-06T20:01:58.9417080Z """ 2025-05-06T20:01:58.9417440Z Dynamically get PR information 2025-05-06T20:01:58.9417881Z """ 2025-05-06T20:01:58.9418345Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-05-06T20:01:58.9418922Z headers = { 2025-05-06T20:01:58.9419328Z "Accept": "application/vnd.github.v3+json", 2025-05-06T20:01:58.9419891Z "Authorization": f"token {github_token}", 2025-05-06T20:01:58.9420380Z } 2025-05-06T20:01:58.9420773Z json_response: dict[str, Any] = download_json( 2025-05-06T20:01:58.9421326Z url=f"{github_api}/issues/{pr_number}", 2025-05-06T20:01:58.9421831Z headers=headers, 2025-05-06T20:01:58.9422210Z ) 2025-05-06T20:01:58.9422396Z 2025-05-06T20:01:58.9422562Z if not json_response: 2025-05-06T20:01:58.9423086Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-05-06T20:01:58.9423648Z return {} 2025-05-06T20:01:58.9423857Z 2025-05-06T20:01:58.9424038Z return json_response 2025-05-06T20:01:58.9424285Z 2025-05-06T20:01:58.9424291Z 2025-05-06T20:01:58.9424657Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-05-06T20:01:58.9425777Z """ 2025-05-06T20:01:58.9426308Z Dynamically get the latest list of labels from the pull request 2025-05-06T20:01:58.9426913Z """ 2025-05-06T20:01:58.9427354Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-05-06T20:01:58.9427914Z return { 2025-05-06T20:01:58.9428442Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-05-06T20:01:58.9429092Z } 2025-05-06T20:01:58.9429274Z 2025-05-06T20:01:58.9429281Z 2025-05-06T20:01:58.9429441Z def main() -> None: 2025-05-06T20:01:58.9429815Z args = parse_args() 2025-05-06T20:01:58.9430067Z 2025-05-06T20:01:58.9430270Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-05-06T20:01:58.9430633Z 2025-05-06T20:01:58.9430813Z # Check if the PR is opt-out 2025-05-06T20:01:58.9431251Z if args.pr_number: 2025-05-06T20:01:58.9431849Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-05-06T20:01:58.9432554Z if OPT_OUT_LABEL in labels: 2025-05-06T20:01:58.9433010Z log.info( 2025-05-06T20:01:58.9433643Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-05-06T20:01:58.9434361Z ) 2025-05-06T20:01:58.9434856Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:01:58.9435711Z sys.exit() 2025-05-06T20:01:58.9435950Z 2025-05-06T20:01:58.9436101Z try: 2025-05-06T20:01:58.9436486Z rollout_state = get_rollout_state_from_issue( 2025-05-06T20:01:58.9437142Z args.github_token, args.github_issue_repo, args.github_issue 2025-05-06T20:01:58.9437725Z ) 2025-05-06T20:01:58.9437913Z 2025-05-06T20:01:58.9438104Z username = get_potential_pr_author( 2025-05-06T20:01:58.9438594Z args.github_token, 2025-05-06T20:01:58.9439027Z args.github_repo, 2025-05-06T20:01:58.9439448Z args.github_actor, 2025-05-06T20:01:58.9439892Z args.github_ref_type, 2025-05-06T20:01:58.9440351Z args.github_branch, 2025-05-06T20:01:58.9440767Z ) 2025-05-06T20:01:58.9440951Z 2025-05-06T20:01:58.9441212Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-05-06T20:01:58.9441628Z 2025-05-06T20:01:58.9441822Z runner_label_prefix = get_runner_prefix( 2025-05-06T20:01:58.9442334Z rollout_state, 2025-05-06T20:01:58.9442767Z (args.github_issue_owner, username), 2025-05-06T20:01:58.9443281Z args.github_branch, 2025-05-06T20:01:58.9443737Z args.eligible_experiments, 2025-05-06T20:01:58.9444197Z is_canary, 2025-05-06T20:01:58.9444572Z ) 2025-05-06T20:01:58.9444755Z 2025-05-06T20:01:58.9445061Z except Exception as e: 2025-05-06T20:01:58.9445589Z log.error( 2025-05-06T20:01:58.9446196Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-05-06T20:01:58.9446910Z ) 2025-05-06T20:01:58.9447096Z 2025-05-06T20:01:58.9447398Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-05-06T20:01:58.9447865Z 2025-05-06T20:01:58.9447872Z 2025-05-06T20:01:58.9448057Z if __name__ == "__main__": 2025-05-06T20:01:58.9448456Z main() 2025-05-06T20:01:58.9448644Z 2025-05-06T20:01:58.9539180Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-05-06T20:01:58.9540007Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-05-06T20:01:58.9571436Z shell: /usr/bin/bash -e {0} 2025-05-06T20:01:58.9571867Z env: 2025-05-06T20:01:58.9572443Z GITHUB_TOKEN: *** 2025-05-06T20:01:58.9572818Z ISSUE_NUMBER: 5132 2025-05-06T20:01:58.9573236Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:01:58.9573704Z ISSUE_OWNER: 2025-05-06T20:01:58.9574067Z CHECK_EXPERIMENTS: 2025-05-06T20:01:58.9574441Z PR_NUMBER: 2025-05-06T20:01:58.9574784Z ##[endgroup] 2025-05-06T20:01:59.3080017Z Defaulting to user installation because normal site-packages is not writeable 2025-05-06T20:01:59.6262355Z Collecting urllib3==1.26.18 2025-05-06T20:01:59.6568125Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-05-06T20:01:59.6770767Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.1 MB/s eta 0:00:00 2025-05-06T20:01:59.6983813Z Collecting PyGithub==2.3.0 2025-05-06T20:01:59.7007498Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-05-06T20:01:59.7422544Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-05-06T20:01:59.7453548Z 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:01:59.7499505Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-05-06T20:01:59.7518429Z 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:01:59.7535503Z 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:01:59.7771512Z Collecting Deprecated (from PyGithub==2.3.0) 2025-05-06T20:01:59.7794710Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-05-06T20:01:59.8020932Z 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:01:59.9082478Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-05-06T20:01:59.9106694Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-05-06T20:02:00.0211860Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-05-06T20:02:00.0236882Z 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:00.0436882Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-05-06T20:02:00.0460479Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-05-06T20:02:00.0677014Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-05-06T20:02:00.0738300Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 30.9 MB/s eta 0:00:00 2025-05-06T20:02:00.0764557Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-05-06T20:02:00.0849041Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 59.2 MB/s eta 0:00:00 2025-05-06T20:02:00.0879062Z 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:00.0994231Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 86.3 MB/s eta 0:00:00 2025-05-06T20:02:00.1017242Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-05-06T20:02:00.1062215Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-05-06T20:02:00.1126160Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 100.2 MB/s eta 0:00:00 2025-05-06T20:02:00.1150913Z 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:00.1190522Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 32.6 MB/s eta 0:00:00 2025-05-06T20:02:00.1215192Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-05-06T20:02:00.1258792Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 38.0 MB/s eta 0:00:00 2025-05-06T20:02:00.4106742Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-05-06T20:02:00.9376419Z 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:01.0160113Z ##[group]Run curr_branch="ciflow/trunk/147470" 2025-05-06T20:02:01.0160525Z curr_branch="ciflow/trunk/147470" 2025-05-06T20:02:01.0160819Z curr_ref_type="tag" 2025-05-06T20:02:01.0161106Z echo "Current branch is '$curr_branch'" 2025-05-06T20:02:01.0161394Z  2025-05-06T20:02:01.0161627Z python3 runner_determinator.py \ 2025-05-06T20:02:01.0161968Z  --github-token "$GITHUB_TOKEN" \ 2025-05-06T20:02:01.0162283Z  --github-issue "$ISSUE_NUMBER" \ 2025-05-06T20:02:01.0162584Z  --github-branch "$curr_branch" \ 2025-05-06T20:02:01.0162885Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-05-06T20:02:01.0163210Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-05-06T20:02:01.0163530Z  --github-ref-type "$curr_ref_type" \ 2025-05-06T20:02:01.0163847Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-05-06T20:02:01.0164248Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-05-06T20:02:01.0164609Z  --pr-number "${PR_NUMBER}" 2025-05-06T20:02:01.0196122Z shell: /usr/bin/bash -e {0} 2025-05-06T20:02:01.0196388Z env: 2025-05-06T20:02:01.0196953Z GITHUB_TOKEN: *** 2025-05-06T20:02:01.0197181Z ISSUE_NUMBER: 5132 2025-05-06T20:02:01.0197433Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-05-06T20:02:01.0197699Z ISSUE_OWNER: 2025-05-06T20:02:01.0197920Z CHECK_EXPERIMENTS: 2025-05-06T20:02:01.0198153Z PR_NUMBER: 2025-05-06T20:02:01.0198365Z ##[endgroup] 2025-05-06T20:02:01.0248164Z Current branch is 'ciflow/trunk/147470' 2025-05-06T20:02:03.5199940Z INFO : Based on rollout percentage of 100%, enabling experiment ephemeral. 2025-05-06T20:02:03.5447650Z INFO : Setting output: label-type='ephemeral.' 2025-05-06T20:02:03.5505814Z Evaluate and set job outputs 2025-05-06T20:02:03.5512494Z Set output 'label-type' 2025-05-06T20:02:03.5514320Z Cleaning up orphan processes