2025-04-21T20:25:12.6491948Z Current runner version: '2.323.0' 2025-04-21T20:25:12.6518227Z ##[group]Operating System 2025-04-21T20:25:12.6519170Z Ubuntu 2025-04-21T20:25:12.6519653Z 24.04.2 2025-04-21T20:25:12.6520171Z LTS 2025-04-21T20:25:12.6520757Z ##[endgroup] 2025-04-21T20:25:12.6521258Z ##[group]Runner Image 2025-04-21T20:25:12.6521850Z Image: ubuntu-24.04 2025-04-21T20:25:12.6522429Z Version: 20250413.1.0 2025-04-21T20:25:12.6523459Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250413.1/images/ubuntu/Ubuntu2404-Readme.md 2025-04-21T20:25:12.6525182Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250413.1 2025-04-21T20:25:12.6526179Z ##[endgroup] 2025-04-21T20:25:12.6526727Z ##[group]Runner Image Provisioner 2025-04-21T20:25:12.6527287Z 2.0.422.1 2025-04-21T20:25:12.6527814Z ##[endgroup] 2025-04-21T20:25:12.6530200Z ##[group]GITHUB_TOKEN Permissions 2025-04-21T20:25:12.6532504Z Actions: read 2025-04-21T20:25:12.6533274Z Attestations: read 2025-04-21T20:25:12.6533931Z Checks: read 2025-04-21T20:25:12.6534740Z Contents: read 2025-04-21T20:25:12.6535289Z Deployments: read 2025-04-21T20:25:12.6535861Z Discussions: read 2025-04-21T20:25:12.6536385Z Issues: read 2025-04-21T20:25:12.6536861Z Metadata: read 2025-04-21T20:25:12.6537400Z Models: read 2025-04-21T20:25:12.6537894Z Packages: read 2025-04-21T20:25:12.6538406Z Pages: read 2025-04-21T20:25:12.6538851Z PullRequests: read 2025-04-21T20:25:12.6539450Z RepositoryProjects: read 2025-04-21T20:25:12.6539997Z SecurityEvents: read 2025-04-21T20:25:12.6540511Z Statuses: read 2025-04-21T20:25:12.6541043Z ##[endgroup] 2025-04-21T20:25:12.6543228Z Secret source: Actions 2025-04-21T20:25:12.6544033Z Prepare workflow directory 2025-04-21T20:25:12.7102679Z Prepare all required actions 2025-04-21T20:25:12.7161570Z Complete job name: unit-test / get-label-type / runner-determinator 2025-04-21T20:25:12.7783911Z ##[group]Run cat < runner_determinator.py 2025-04-21T20:25:12.7786691Z cat < runner_determinator.py 2025-04-21T20:25:12.7787367Z # flake8: noqa: G004 2025-04-21T20:25:12.7787975Z  2025-04-21T20:25:12.7788718Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-04-21T20:25:12.7789769Z # must be kept in sync. You can do it easily by running the following command: 2025-04-21T20:25:12.7790727Z # python .github/scripts/update_runner_determinator.py 2025-04-21T20:25:12.7791413Z  2025-04-21T20:25:12.7791859Z """ 2025-04-21T20:25:12.7792527Z This runner determinator is used to determine which set of runners to run a 2025-04-21T20:25:12.7793593Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-04-21T20:25:12.7794876Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-04-21T20:25:12.7795919Z of which runners should be used to run which job. 2025-04-21T20:25:12.7796565Z  2025-04-21T20:25:12.7797259Z The configuration has two parts, the settings and a list of opted-in users, 2025-04-21T20:25:12.7798349Z separated by a line containing "---". If the line is not present, the 2025-04-21T20:25:12.7799306Z settings are considered to be empty with only the second part, the user 2025-04-21T20:25:12.7800108Z list, defined. 2025-04-21T20:25:12.7800648Z  2025-04-21T20:25:12.7801326Z The first part is a YAML block that defines the rollout settings. This can be 2025-04-21T20:25:12.7802367Z used to define any settings that are needed to determine which runners to use. 2025-04-21T20:25:12.7803388Z It's fields are defined by the RolloutSettings class below. 2025-04-21T20:25:12.7804116Z  2025-04-21T20:25:12.7805061Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-04-21T20:25:12.7806141Z The user list is also a comma separated list of additional features or 2025-04-21T20:25:12.7807352Z experiments which the user could be opted in to. 2025-04-21T20:25:12.7808006Z  2025-04-21T20:25:12.7808593Z The user list has the following rules: 2025-04-21T20:25:12.7809191Z  2025-04-21T20:25:12.7809800Z - Users are GitHub usernames, which must start with the @ prefix 2025-04-21T20:25:12.7810810Z - Each user is also a comma-separated list of features/experiments to enable 2025-04-21T20:25:12.7811713Z - A "#" prefix opts the user out of all experiments 2025-04-21T20:25:12.7812346Z  2025-04-21T20:25:12.7812825Z Example config: 2025-04-21T20:25:12.7813445Z  # A list of experiments that can be opted into. 2025-04-21T20:25:12.7814201Z  # This defines the behavior they'll induce when opted into. 2025-04-21T20:25:12.7815264Z  # Expected syntax is: 2025-04-21T20:25:12.7816055Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-04-21T20:25:12.7817311Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-04-21T20:25:12.7818227Z  2025-04-21T20:25:12.7818672Z  experiments: 2025-04-21T20:25:12.7819215Z  lf: 2025-04-21T20:25:12.7819719Z  rollout_percent: 25 2025-04-21T20:25:12.7820337Z  all_branches: false 2025-04-21T20:25:12.7820927Z  default: true 2025-04-21T20:25:12.7821464Z  --- 2025-04-21T20:25:12.7821976Z  2025-04-21T20:25:12.7822393Z  # Opt-ins: 2025-04-21T20:25:12.7823170Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-04-21T20:25:12.7824577Z  # and specifying experiments to enable in a comma-separated list. 2025-04-21T20:25:12.7825598Z  # To always opt out of an experiment, prefix it with a "-". 2025-04-21T20:25:12.7826477Z  # Experiments should be from the above list. 2025-04-21T20:25:12.7827104Z  2025-04-21T20:25:12.7827568Z  @User1,-lf,split_build 2025-04-21T20:25:12.7828110Z  @User2,lf 2025-04-21T20:25:12.7828706Z  @User3,split_build 2025-04-21T20:25:12.7829221Z """ 2025-04-21T20:25:12.7829779Z  2025-04-21T20:25:12.7830271Z import json 2025-04-21T20:25:12.7830754Z import logging 2025-04-21T20:25:12.7831319Z import os 2025-04-21T20:25:12.7831872Z import random 2025-04-21T20:25:12.7832398Z import re 2025-04-21T20:25:12.7832948Z import sys 2025-04-21T20:25:12.7833482Z from argparse import ArgumentParser 2025-04-21T20:25:12.7834176Z from collections.abc import Iterable 2025-04-21T20:25:12.7834949Z from functools import cache 2025-04-21T20:25:12.7835641Z from logging import LogRecord 2025-04-21T20:25:12.7836249Z from typing import Any, NamedTuple 2025-04-21T20:25:12.7836954Z from urllib.request import Request, urlopen 2025-04-21T20:25:12.7837714Z  2025-04-21T20:25:12.7838155Z import yaml 2025-04-21T20:25:12.7838729Z from github import Auth, Github 2025-04-21T20:25:12.7839330Z from github.Issue import Issue 2025-04-21T20:25:12.7839927Z  2025-04-21T20:25:12.7840318Z  2025-04-21T20:25:12.7840908Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-04-21T20:25:12.7841756Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-04-21T20:25:12.7842695Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-04-21T20:25:12.7843570Z  2025-04-21T20:25:12.7844110Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-04-21T20:25:12.7845139Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-04-21T20:25:12.7845864Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-04-21T20:25:12.7846868Z OPT_OUT_LABEL = "no-runner-experiments" 2025-04-21T20:25:12.7847480Z  2025-04-21T20:25:12.7848025Z SETTING_EXPERIMENTS = "experiments" 2025-04-21T20:25:12.7848648Z  2025-04-21T20:25:12.7849084Z LF_FLEET_EXPERIMENT = "lf" 2025-04-21T20:25:12.7849762Z CANARY_FLEET_SUFFIX = ".c" 2025-04-21T20:25:12.7850283Z  2025-04-21T20:25:12.7850701Z  2025-04-21T20:25:12.7851224Z class Experiment(NamedTuple): 2025-04-21T20:25:12.7851842Z  rollout_perc: float = ( 2025-04-21T20:25:12.7852612Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-04-21T20:25:12.7853446Z  ) 2025-04-21T20:25:12.7853943Z  all_branches: bool = ( 2025-04-21T20:25:12.7855024Z  False # If True, the experiment is also enabled on the exception branches 2025-04-21T20:25:12.7855894Z  ) 2025-04-21T20:25:12.7856390Z  default: bool = ( 2025-04-21T20:25:12.7857074Z  True # If True, the experiment is enabled by default for all queries 2025-04-21T20:25:12.7857911Z  ) 2025-04-21T20:25:12.7858345Z  2025-04-21T20:25:12.7858808Z  # Add more fields as needed 2025-04-21T20:25:12.7859435Z  2025-04-21T20:25:12.7859873Z  2025-04-21T20:25:12.7860309Z class Settings(NamedTuple): 2025-04-21T20:25:12.7860946Z  """ 2025-04-21T20:25:12.7861551Z  Settings for the experiments that can be opted into. 2025-04-21T20:25:12.7862208Z  """ 2025-04-21T20:25:12.7862731Z  2025-04-21T20:25:12.7863209Z  experiments: dict[str, Experiment] = {} 2025-04-21T20:25:12.7863848Z  2025-04-21T20:25:12.7864769Z  2025-04-21T20:25:12.7865459Z class ColorFormatter(logging.Formatter): 2025-04-21T20:25:12.7866288Z  """Color codes the log messages based on the log level""" 2025-04-21T20:25:12.7867061Z  2025-04-21T20:25:12.7867524Z  COLORS = { 2025-04-21T20:25:12.7868056Z  "WARNING": "\033[33m", # Yellow 2025-04-21T20:25:12.7868740Z  "ERROR": "\033[31m", # Red 2025-04-21T20:25:12.7869325Z  "CRITICAL": "\033[31m", # Red 2025-04-21T20:25:12.7869991Z  "INFO": "\033[0m", # Reset 2025-04-21T20:25:12.7870653Z  "DEBUG": "\033[0m", # Reset 2025-04-21T20:25:12.7871206Z  } 2025-04-21T20:25:12.7871691Z  2025-04-21T20:25:12.7872221Z  def format(self, record: LogRecord) -> str: 2025-04-21T20:25:12.7873115Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-04-21T20:25:12.7874000Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-04-21T20:25:12.7874944Z  return super().format(record) 2025-04-21T20:25:12.7875590Z  2025-04-21T20:25:12.7876002Z  2025-04-21T20:25:12.7876536Z handler = logging.StreamHandler() 2025-04-21T20:25:12.7877349Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-04-21T20:25:12.7878193Z  2025-04-21T20:25:12.7878745Z log = logging.getLogger(os.path.basename(__file__)) 2025-04-21T20:25:12.7879488Z log.addHandler(handler) 2025-04-21T20:25:12.7880318Z log.setLevel(logging.INFO) 2025-04-21T20:25:12.7880874Z  2025-04-21T20:25:12.7881345Z  2025-04-21T20:25:12.7881872Z def set_github_output(key: str, value: str) -> None: 2025-04-21T20:25:12.7882581Z  """ 2025-04-21T20:25:12.7883219Z  Defines outputs of the github action that invokes this script 2025-04-21T20:25:12.7883950Z  """ 2025-04-21T20:25:12.7884724Z  if not GITHUB_OUTPUT: 2025-04-21T20:25:12.7885981Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-04-21T20:25:12.7887372Z  log.warning( 2025-04-21T20:25:12.7888382Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-04-21T20:25:12.7889436Z  ) 2025-04-21T20:25:12.7890000Z  print(f"::set-output name={key}::{value}") 2025-04-21T20:25:12.7890657Z  return 2025-04-21T20:25:12.7891195Z  2025-04-21T20:25:12.7891659Z  with open(GITHUB_OUTPUT, "a") as f: 2025-04-21T20:25:12.7892376Z  log.info(f"Setting output: {key}='{value}'") 2025-04-21T20:25:12.7893069Z  f.write(f"{key}={value}\n") 2025-04-21T20:25:12.7893667Z  2025-04-21T20:25:12.7894163Z  2025-04-21T20:25:12.7895086Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-04-21T20:25:12.7895859Z  return frozenset( 2025-04-21T20:25:12.7896634Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-04-21T20:25:12.7897453Z  ) 2025-04-21T20:25:12.7897878Z  2025-04-21T20:25:12.7898348Z  2025-04-21T20:25:12.7898845Z def parse_args() -> Any: 2025-04-21T20:25:12.7899519Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-04-21T20:25:12.7900540Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-04-21T20:25:12.7901387Z  parser.add_argument( 2025-04-21T20:25:12.7901979Z  "--github-issue-repo", 2025-04-21T20:25:12.7902621Z  type=str, 2025-04-21T20:25:12.7903131Z  required=False, 2025-04-21T20:25:12.7903863Z  default="pytorch/test-infra", 2025-04-21T20:25:12.7904831Z  help="GitHub repo to get the issue", 2025-04-21T20:25:12.7905516Z  ) 2025-04-21T20:25:12.7906001Z  parser.add_argument( 2025-04-21T20:25:12.7906628Z  "--github-repo", 2025-04-21T20:25:12.7907222Z  type=str, 2025-04-21T20:25:12.7907722Z  required=True, 2025-04-21T20:25:12.7908378Z  help="GitHub repo where CI is running", 2025-04-21T20:25:12.7908985Z  ) 2025-04-21T20:25:12.7909472Z  parser.add_argument( 2025-04-21T20:25:12.7978462Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-04-21T20:25:12.7979887Z  ) 2025-04-21T20:25:12.7980566Z  parser.add_argument( 2025-04-21T20:25:12.7981817Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-04-21T20:25:12.7983075Z  ) 2025-04-21T20:25:12.7983855Z  parser.add_argument( 2025-04-21T20:25:12.7985154Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-04-21T20:25:12.7985914Z  ) 2025-04-21T20:25:12.7986323Z  parser.add_argument( 2025-04-21T20:25:12.7987040Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-04-21T20:25:12.7987762Z  ) 2025-04-21T20:25:12.7988173Z  parser.add_argument( 2025-04-21T20:25:12.7988673Z  "--github-ref-type", 2025-04-21T20:25:12.7989163Z  type=str, 2025-04-21T20:25:12.7989604Z  required=True, 2025-04-21T20:25:12.7990150Z  help="Current GitHub ref type, branch or tag", 2025-04-21T20:25:12.7990703Z  ) 2025-04-21T20:25:12.7991097Z  parser.add_argument( 2025-04-21T20:25:12.7991613Z  "--eligible-experiments", 2025-04-21T20:25:12.7992172Z  type=_str_comma_separated_to_set, 2025-04-21T20:25:12.7992710Z  required=False, 2025-04-21T20:25:12.7993491Z  default="", 2025-04-21T20:25:12.7994631Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-04-21T20:25:12.7995640Z  ) 2025-04-21T20:25:12.7996038Z  parser.add_argument( 2025-04-21T20:25:12.7996519Z  "--pr-number", 2025-04-21T20:25:12.7996979Z  type=str, 2025-04-21T20:25:12.7997421Z  required=False, 2025-04-21T20:25:12.7997883Z  default="", 2025-04-21T20:25:12.7998411Z  help="the optional PR number where this is run", 2025-04-21T20:25:12.7998972Z  ) 2025-04-21T20:25:12.7999352Z  2025-04-21T20:25:12.7999733Z  return parser.parse_args() 2025-04-21T20:25:12.8000222Z  2025-04-21T20:25:12.8000575Z  2025-04-21T20:25:12.8001202Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-04-21T20:25:12.8001984Z  auth = Auth.Token(github_token) 2025-04-21T20:25:12.8002511Z  return Github(auth=auth) 2025-04-21T20:25:12.8002989Z  2025-04-21T20:25:12.8003334Z  2025-04-21T20:25:12.8004001Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-04-21T20:25:12.8005186Z  repo = gh.get_repo(repo) 2025-04-21T20:25:12.8005736Z  return repo.get_issue(number=issue_num) 2025-04-21T20:25:12.8006255Z  2025-04-21T20:25:12.8006607Z  2025-04-21T20:25:12.8006984Z def get_potential_pr_author( 2025-04-21T20:25:12.8007675Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-04-21T20:25:12.8008382Z ) -> str: 2025-04-21T20:25:12.8009102Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-04-21T20:25:12.8009940Z  # Fetch the actual username from the original PR. The PR number is 2025-04-21T20:25:12.8010717Z  # embedded in the tag name: ciflow// 2025-04-21T20:25:12.8011307Z  2025-04-21T20:25:12.8011711Z  gh = get_gh_client(github_token) 2025-04-21T20:25:12.8012209Z  2025-04-21T20:25:12.8012700Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-04-21T20:25:12.8013350Z  split_tag = ref_name.split("/") 2025-04-21T20:25:12.8013860Z  if ( 2025-04-21T20:25:12.8014281Z  len(split_tag) == 3 2025-04-21T20:25:12.8015040Z  and split_tag[0] == "ciflow" 2025-04-21T20:25:12.8015598Z  and split_tag[2].isnumeric() 2025-04-21T20:25:12.8016104Z  ): 2025-04-21T20:25:12.8016543Z  pr_number = split_tag[2] 2025-04-21T20:25:12.8017053Z  try: 2025-04-21T20:25:12.8017528Z  repository = gh.get_repo(repo) 2025-04-21T20:25:12.8018164Z  pull = repository.get_pull(number=int(pr_number)) 2025-04-21T20:25:12.8018795Z  except Exception as e: 2025-04-21T20:25:12.8019351Z  raise Exception( # noqa: TRY002 2025-04-21T20:25:12.8020038Z  f"issue with pull request {pr_number} from repo {repository}" 2025-04-21T20:25:12.8020687Z  ) from e 2025-04-21T20:25:12.8021265Z  return pull.user.login # type: ignore[no-any-return] 2025-04-21T20:25:12.8021983Z  # In all other cases, return the original input username 2025-04-21T20:25:12.8022580Z  return username 2025-04-21T20:25:12.8023014Z  2025-04-21T20:25:12.8023365Z  2025-04-21T20:25:12.8023804Z def is_exception_branch(branch: str) -> bool: 2025-04-21T20:25:12.8024788Z  """ 2025-04-21T20:25:12.8025506Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-04-21T20:25:12.8026550Z  """ 2025-04-21T20:25:12.8027122Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-04-21T20:25:12.8027792Z  2025-04-21T20:25:12.8028130Z  2025-04-21T20:25:12.8028527Z def load_yaml(yaml_text: str) -> Any: 2025-04-21T20:25:12.8029037Z  try: 2025-04-21T20:25:12.8029459Z  data = yaml.safe_load(yaml_text) 2025-04-21T20:25:12.8030017Z  return data 2025-04-21T20:25:12.8030475Z  except yaml.YAMLError: 2025-04-21T20:25:12.8031007Z  log.exception("Error loading YAML") 2025-04-21T20:25:12.8031527Z  raise 2025-04-21T20:25:12.8031928Z  2025-04-21T20:25:12.8032277Z  2025-04-21T20:25:12.8032910Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-04-21T20:25:12.8033650Z  """ 2025-04-21T20:25:12.8034301Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-04-21T20:25:12.8035534Z  2025-04-21T20:25:12.8036090Z  If the issue body contains "---" then the text above that is the settings 2025-04-21T20:25:12.8036865Z  and the text below is the list of opted in users. 2025-04-21T20:25:12.8037432Z  2025-04-21T20:25:12.8038011Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-04-21T20:25:12.8038713Z  """ 2025-04-21T20:25:12.8039201Z  rollout_state_parts = rollout_state.split("---") 2025-04-21T20:25:12.8039820Z  if len(rollout_state_parts) >= 2: 2025-04-21T20:25:12.8040587Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-04-21T20:25:12.8041200Z  else: 2025-04-21T20:25:12.8041620Z  return "", rollout_state 2025-04-21T20:25:12.8042107Z  2025-04-21T20:25:12.8042450Z  2025-04-21T20:25:12.8042854Z class UserOptins(dict[str, list[str]]): 2025-04-21T20:25:12.8043383Z  """ 2025-04-21T20:25:12.8043936Z  Dictionary of users with a list of features they have opted into 2025-04-21T20:25:12.8044838Z  """ 2025-04-21T20:25:12.8045215Z  2025-04-21T20:25:12.8045557Z  2025-04-21T20:25:12.8046098Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-04-21T20:25:12.8046762Z  """ 2025-04-21T20:25:12.8047494Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-04-21T20:25:12.8048316Z  2025-04-21T20:25:12.8049123Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-04-21T20:25:12.8050103Z  - Example line: "@User1,lf,split_build" 2025-04-21T20:25:12.8050811Z  - A "#" prefix indicates the user is opted out of all experiments 2025-04-21T20:25:12.8051452Z  2025-04-21T20:25:12.8051788Z  2025-04-21T20:25:12.8052131Z  """ 2025-04-21T20:25:12.8052515Z  optins = UserOptins() 2025-04-21T20:25:12.8053050Z  for user in user_optin_text.split("\n"): 2025-04-21T20:25:12.8053640Z  user = user.strip("\r\n\t -") 2025-04-21T20:25:12.8054217Z  if not user or not user.startswith("@"): 2025-04-21T20:25:12.8055027Z  # Not a valid user. Skip 2025-04-21T20:25:12.8055532Z  continue 2025-04-21T20:25:12.8055953Z  2025-04-21T20:25:12.8056300Z  if user: 2025-04-21T20:25:12.8056799Z  usr_name = user.split(",")[0].strip("@") 2025-04-21T20:25:12.8057499Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-04-21T20:25:12.8058292Z  2025-04-21T20:25:12.8058655Z  return optins 2025-04-21T20:25:12.8059078Z  2025-04-21T20:25:12.8059413Z  2025-04-21T20:25:12.8059907Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-04-21T20:25:12.8060518Z  """ 2025-04-21T20:25:12.8060942Z  Check if the experiment name is valid. 2025-04-21T20:25:12.8061478Z  A valid name: 2025-04-21T20:25:12.8062153Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-04-21T20:25:12.8063100Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-04-21T20:25:12.8063817Z  - Cannot contain spaces 2025-04-21T20:25:12.8064292Z  """ 2025-04-21T20:25:12.8064790Z  2025-04-21T20:25:12.8065253Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-04-21T20:25:12.8065979Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-04-21T20:25:12.8066572Z  2025-04-21T20:25:12.8066918Z  if valid: 2025-04-21T20:25:12.8067333Z  return True 2025-04-21T20:25:12.8067759Z  2025-04-21T20:25:12.8068113Z  log.error( 2025-04-21T20:25:12.8069542Z  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-04-21T20:25:12.8071025Z  ) 2025-04-21T20:25:12.8071403Z  return False 2025-04-21T20:25:12.8071814Z  2025-04-21T20:25:12.8072148Z  2025-04-21T20:25:12.8072791Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-04-21T20:25:12.8073440Z  """ 2025-04-21T20:25:12.8074048Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-04-21T20:25:12.8075015Z  """ 2025-04-21T20:25:12.8075379Z  try: 2025-04-21T20:25:12.8075774Z  if settings_text: 2025-04-21T20:25:12.8076525Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-04-21T20:25:12.8077308Z  # for easy reading 2025-04-21T20:25:12.8078132Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-04-21T20:25:12.8079014Z  # the backtick character in shell commands. 2025-04-21T20:25:12.8079629Z  backtick = chr(96) # backtick character 2025-04-21T20:25:12.8080305Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-04-21T20:25:12.8080978Z  settings = load_yaml(settings_text) 2025-04-21T20:25:12.8081503Z  2025-04-21T20:25:12.8082090Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-04-21T20:25:12.8082835Z  experiments = {} 2025-04-21T20:25:12.8083297Z  2025-04-21T20:25:12.8083869Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-04-21T20:25:12.8084744Z  if not is_valid_experiment_name(exp_name): 2025-04-21T20:25:12.8085836Z  # 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-04-21T20:25:12.8086848Z  continue 2025-04-21T20:25:12.8087306Z  2025-04-21T20:25:12.8087694Z  valid_settings = {} 2025-04-21T20:25:12.8088235Z  for setting in exp_settings: 2025-04-21T20:25:12.8088878Z  if setting not in Experiment._fields: 2025-04-21T20:25:12.8089705Z  log.warning( 2025-04-21T20:25:12.8090434Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-04-21T20:25:12.8091143Z  ) 2025-04-21T20:25:12.8091596Z  else: 2025-04-21T20:25:12.8092158Z  valid_settings[setting] = exp_settings[setting] 2025-04-21T20:25:12.8092724Z  2025-04-21T20:25:12.8093214Z  experiments[exp_name] = Experiment(**valid_settings) 2025-04-21T20:25:12.8093865Z  return Settings(experiments) 2025-04-21T20:25:12.8094550Z  2025-04-21T20:25:12.8094996Z  except Exception: 2025-04-21T20:25:12.8095522Z  log.exception("Failed to parse settings") 2025-04-21T20:25:12.8096091Z  2025-04-21T20:25:12.8096458Z  return Settings() 2025-04-21T20:25:12.8096886Z  2025-04-21T20:25:12.8097223Z  2025-04-21T20:25:12.8097681Z def parse_settings(rollout_state: str) -> Settings: 2025-04-21T20:25:12.8098260Z  """ 2025-04-21T20:25:12.8098721Z  Parse settings, if any, from the rollout state. 2025-04-21T20:25:12.8099274Z  2025-04-21T20:25:12.8099815Z  If the issue body contains "---" then the text above that is the settings 2025-04-21T20:25:12.8100595Z  and the text below is the list of opted in users. 2025-04-21T20:25:12.8101160Z  2025-04-21T20:25:12.8101755Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-04-21T20:25:12.8102476Z  """ 2025-04-21T20:25:12.8103054Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-04-21T20:25:12.8103987Z  return parse_settings_from_text(settings_text) 2025-04-21T20:25:12.8104766Z  2025-04-21T20:25:12.8105139Z  2025-04-21T20:25:12.8105611Z def parse_users(rollout_state: str) -> UserOptins: 2025-04-21T20:25:12.8106176Z  """ 2025-04-21T20:25:12.8106596Z  Parse users from the rollout state. 2025-04-21T20:25:12.8107102Z  2025-04-21T20:25:12.8107442Z  """ 2025-04-21T20:25:12.8107998Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-04-21T20:25:12.8108746Z  return parse_user_opt_in_from_text(users_text) 2025-04-21T20:25:12.8109283Z  2025-04-21T20:25:12.8109624Z  2025-04-21T20:25:12.8110242Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-04-21T20:25:12.8110977Z  """ 2025-04-21T20:25:12.8111425Z  Check if a user is opted into an experiment 2025-04-21T20:25:12.8111970Z  """ 2025-04-21T20:25:12.8112463Z  return experiment_name in user_optins.get(user, []) 2025-04-21T20:25:12.8113041Z  2025-04-21T20:25:12.8113388Z  2025-04-21T20:25:12.8114017Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-04-21T20:25:12.8114974Z  """ 2025-04-21T20:25:12.8115466Z  Check if a user explicitly opted out of an experiment 2025-04-21T20:25:12.8116038Z  """ 2025-04-21T20:25:12.8116574Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-04-21T20:25:12.8117288Z  experiment_optout = "-" + experiment_name 2025-04-21T20:25:12.8117952Z  if experiment_optout not in user_optins.get(user, []): 2025-04-21T20:25:12.8118550Z  return False 2025-04-21T20:25:12.8118968Z  2025-04-21T20:25:12.8119441Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-04-21T20:25:12.8120039Z  log.warning( 2025-04-21T20:25:12.8120859Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-04-21T20:25:12.8121874Z  ) 2025-04-21T20:25:12.8122257Z  2025-04-21T20:25:12.8122609Z  return True 2025-04-21T20:25:12.8123030Z  2025-04-21T20:25:12.8123373Z  2025-04-21T20:25:12.8123742Z def get_runner_prefix( 2025-04-21T20:25:12.8124213Z  rollout_state: str, 2025-04-21T20:25:12.8124835Z  workflow_requestors: Iterable[str], 2025-04-21T20:25:12.8125356Z  branch: str, 2025-04-21T20:25:12.8125890Z  eligible_experiments: frozenset[str] = frozenset(), 2025-04-21T20:25:12.8126503Z  is_canary: bool = False, 2025-04-21T20:25:12.8126968Z ) -> str: 2025-04-21T20:25:12.8127431Z  settings = parse_settings(rollout_state) 2025-04-21T20:25:12.8128025Z  user_optins = parse_users(rollout_state) 2025-04-21T20:25:12.8128533Z  2025-04-21T20:25:12.8128905Z  fleet_prefix = "" 2025-04-21T20:25:12.8129350Z  prefixes = [] 2025-04-21T20:25:12.8130053Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-04-21T20:25:12.8130988Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-04-21T20:25:12.8131688Z  log.info( 2025-04-21T20:25:12.8132391Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-04-21T20:25:12.8133126Z  ) 2025-04-21T20:25:12.8133527Z  continue 2025-04-21T20:25:12.8133945Z  2025-04-21T20:25:12.8134416Z  if eligible_experiments: 2025-04-21T20:25:12.8135131Z  if experiment_name not in eligible_experiments: 2025-04-21T20:25:12.8135798Z  exp_list = ", ".join(eligible_experiments) 2025-04-21T20:25:12.8136358Z  log.info( 2025-04-21T20:25:12.8137154Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-04-21T20:25:12.8137987Z  ) 2025-04-21T20:25:12.8138414Z  continue 2025-04-21T20:25:12.8138934Z  elif not experiment_settings.default: 2025-04-21T20:25:12.8139467Z  log.info( 2025-04-21T20:25:12.8140154Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-04-21T20:25:12.8140882Z  ) 2025-04-21T20:25:12.8141280Z  continue 2025-04-21T20:25:12.8141701Z  2025-04-21T20:25:12.8142173Z  # Is any workflow_requestor opted out to this experiment? 2025-04-21T20:25:12.8142803Z  opted_out_users = [ 2025-04-21T20:25:12.8143293Z  requestor 2025-04-21T20:25:12.8143796Z  for requestor in workflow_requestors 2025-04-21T20:25:12.8144758Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-04-21T20:25:12.8145453Z  ] 2025-04-21T20:25:12.8145831Z  2025-04-21T20:25:12.8146197Z  if opted_out_users: 2025-04-21T20:25:12.8146677Z  log.info( 2025-04-21T20:25:12.8147329Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-04-21T20:25:12.8148019Z  ) 2025-04-21T20:25:12.8148426Z  continue 2025-04-21T20:25:12.8148850Z  2025-04-21T20:25:12.8149322Z  # Is any workflow_requestor opted in to this experiment? 2025-04-21T20:25:12.8149939Z  opted_in_users = [ 2025-04-21T20:25:12.8150423Z  requestor 2025-04-21T20:25:12.8150913Z  for requestor in workflow_requestors 2025-04-21T20:25:12.8151597Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-04-21T20:25:12.8152373Z  ] 2025-04-21T20:25:12.8152744Z  2025-04-21T20:25:12.8153105Z  enabled = False 2025-04-21T20:25:12.8153569Z  if opted_in_users: 2025-04-21T20:25:12.8154038Z  log.info( 2025-04-21T20:25:12.8155001Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-04-21T20:25:12.8155687Z  ) 2025-04-21T20:25:12.8156098Z  enabled = True 2025-04-21T20:25:12.8156543Z  2025-04-21T20:25:12.8156953Z  elif experiment_settings.rollout_perc: 2025-04-21T20:25:12.8157790Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-04-21T20:25:12.8158713Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-04-21T20:25:12.8159356Z  log.info( 2025-04-21T20:25:12.8160232Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-04-21T20:25:12.8161122Z  ) 2025-04-21T20:25:12.8161548Z  enabled = True 2025-04-21T20:25:12.8162021Z  2025-04-21T20:25:12.8162370Z  if enabled: 2025-04-21T20:25:12.8162842Z  label = experiment_name 2025-04-21T20:25:12.8163408Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-04-21T20:25:12.8164230Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-04-21T20:25:12.8165344Z  # - If it's enabled, then we always list it's prefix first 2025-04-21T20:25:12.8166128Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-04-21T20:25:12.8166793Z  if is_canary: 2025-04-21T20:25:12.8167308Z  label += CANARY_FLEET_SUFFIX 2025-04-21T20:25:12.8167862Z  fleet_prefix = label 2025-04-21T20:25:12.8168355Z  else: 2025-04-21T20:25:12.8168812Z  prefixes.append(label) 2025-04-21T20:25:12.8169314Z  2025-04-21T20:25:12.8169681Z  if len(prefixes) > 1: 2025-04-21T20:25:12.8170158Z  log.error( 2025-04-21T20:25:12.8171185Z  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-04-21T20:25:12.8172259Z  ) 2025-04-21T20:25:12.8172670Z  prefixes = prefixes[:1] 2025-04-21T20:25:12.8173145Z  2025-04-21T20:25:12.8173531Z  # Fleet always comes first 2025-04-21T20:25:12.8174016Z  if fleet_prefix: 2025-04-21T20:25:12.8174629Z  prefixes.insert(0, fleet_prefix) 2025-04-21T20:25:12.8175135Z  2025-04-21T20:25:12.8175583Z  return ".".join(prefixes) + "." if prefixes else "" 2025-04-21T20:25:12.8176138Z  2025-04-21T20:25:12.8176490Z  2025-04-21T20:25:12.8177126Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-04-21T20:25:12.8177876Z  """ 2025-04-21T20:25:12.8178489Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-04-21T20:25:12.8179172Z  2025-04-21T20:25:12.8179758Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-04-21T20:25:12.8180452Z  """ 2025-04-21T20:25:12.8180877Z  gh = get_gh_client(github_token) 2025-04-21T20:25:12.8181450Z  issue = get_issue(gh, repo, issue_num) 2025-04-21T20:25:12.8182106Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-04-21T20:25:12.8182845Z  2025-04-21T20:25:12.8183198Z  2025-04-21T20:25:12.8183798Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-04-21T20:25:12.8184653Z  for _ in range(num_retries): 2025-04-21T20:25:12.8185149Z  try: 2025-04-21T20:25:12.8185605Z  req = Request(url=url, headers=headers) 2025-04-21T20:25:12.8186262Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-04-21T20:25:12.8186905Z  return json.loads(content) 2025-04-21T20:25:12.8187435Z  except Exception as e: 2025-04-21T20:25:12.8188003Z  log.warning(f"Could not download {url}: {e}") 2025-04-21T20:25:12.8188545Z  2025-04-21T20:25:12.8189115Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-04-21T20:25:12.8189823Z  return {} 2025-04-21T20:25:12.8190218Z  2025-04-21T20:25:12.8190553Z  2025-04-21T20:25:12.8190899Z @cache 2025-04-21T20:25:12.8191544Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-04-21T20:25:12.8192276Z  """ 2025-04-21T20:25:12.8192694Z  Dynamically get PR information 2025-04-21T20:25:12.8193185Z  """ 2025-04-21T20:25:12.8193705Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-04-21T20:25:12.8194526Z  headers = { 2025-04-21T20:25:12.8195182Z  "Accept": "application/vnd.github.v3+json", 2025-04-21T20:25:12.8195810Z  "Authorization": f"token {github_token}", 2025-04-21T20:25:12.8196337Z  } 2025-04-21T20:25:12.8196954Z  json_response: dict[str, Any] = download_json( 2025-04-21T20:25:12.8197577Z  url=f"{github_api}/issues/{pr_number}", 2025-04-21T20:25:12.8198127Z  headers=headers, 2025-04-21T20:25:12.8198573Z  ) 2025-04-21T20:25:12.8198933Z  2025-04-21T20:25:12.8199296Z  if not json_response: 2025-04-21T20:25:12.8199887Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-04-21T20:25:12.8200510Z  return {} 2025-04-21T20:25:12.8200923Z  2025-04-21T20:25:12.8201293Z  return json_response 2025-04-21T20:25:12.8201737Z  2025-04-21T20:25:12.8202081Z  2025-04-21T20:25:12.8202670Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-04-21T20:25:12.8203377Z  """ 2025-04-21T20:25:12.8203924Z  Dynamically get the latest list of labels from the pull request 2025-04-21T20:25:12.8204810Z  """ 2025-04-21T20:25:12.8205329Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-04-21T20:25:12.8205933Z  return { 2025-04-21T20:25:12.8206558Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-04-21T20:25:12.8207243Z  } 2025-04-21T20:25:12.8207598Z  2025-04-21T20:25:12.8207943Z  2025-04-21T20:25:12.8208294Z def main() -> None: 2025-04-21T20:25:12.8208747Z  args = parse_args() 2025-04-21T20:25:12.8209180Z  2025-04-21T20:25:12.8209599Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-04-21T20:25:12.8210134Z  2025-04-21T20:25:12.8210515Z  # Check if the PR is opt-out 2025-04-21T20:25:12.8211022Z  if args.pr_number: 2025-04-21T20:25:12.8211760Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-04-21T20:25:12.8212512Z  if OPT_OUT_LABEL in labels: 2025-04-21T20:25:12.8213019Z  log.info( 2025-04-21T20:25:12.8213744Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-04-21T20:25:12.8214775Z  ) 2025-04-21T20:25:12.8215363Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-04-21T20:25:12.8216027Z  sys.exit() 2025-04-21T20:25:12.8216459Z  2025-04-21T20:25:12.8216796Z  try: 2025-04-21T20:25:12.8217255Z  rollout_state = get_rollout_state_from_issue( 2025-04-21T20:25:12.8217972Z  args.github_token, args.github_issue_repo, args.github_issue 2025-04-21T20:25:12.8218592Z  ) 2025-04-21T20:25:12.8218958Z  2025-04-21T20:25:12.8219363Z  username = get_potential_pr_author( 2025-04-21T20:25:12.8219905Z  args.github_token, 2025-04-21T20:25:12.8220411Z  args.github_repo, 2025-04-21T20:25:12.8220898Z  args.github_actor, 2025-04-21T20:25:12.8221419Z  args.github_ref_type, 2025-04-21T20:25:12.8221929Z  args.github_branch, 2025-04-21T20:25:12.8222399Z  ) 2025-04-21T20:25:12.8222765Z  2025-04-21T20:25:12.8223251Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-04-21T20:25:12.8223842Z  2025-04-21T20:25:12.8224253Z  runner_label_prefix = get_runner_prefix( 2025-04-21T20:25:12.8224915Z  rollout_state, 2025-04-21T20:25:12.8225432Z  (args.github_issue_owner, username), 2025-04-21T20:25:12.8225987Z  args.github_branch, 2025-04-21T20:25:12.8226505Z  args.eligible_experiments, 2025-04-21T20:25:12.8227026Z  is_canary, 2025-04-21T20:25:12.8227584Z  ) 2025-04-21T20:25:12.8227965Z  2025-04-21T20:25:12.8228334Z  except Exception as e: 2025-04-21T20:25:12.8228811Z  log.error( 2025-04-21T20:25:12.8229510Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-04-21T20:25:12.8230277Z  ) 2025-04-21T20:25:12.8230655Z  2025-04-21T20:25:12.8231178Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-04-21T20:25:12.8231821Z  2025-04-21T20:25:12.8232159Z  2025-04-21T20:25:12.8232524Z if __name__ == "__main__": 2025-04-21T20:25:12.8232989Z  main() 2025-04-21T20:25:12.8233379Z  2025-04-21T20:25:12.8233731Z EOF 2025-04-21T20:25:12.8234078Z  2025-04-21T20:25:12.8234718Z cat runner_determinator.py 2025-04-21T20:25:12.8609283Z shell: /usr/bin/bash -e {0} 2025-04-21T20:25:12.8610165Z env: 2025-04-21T20:25:12.8610905Z GITHUB_TOKEN: *** 2025-04-21T20:25:12.8611302Z ISSUE_NUMBER: 5132 2025-04-21T20:25:12.8611737Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-04-21T20:25:12.8612233Z ISSUE_OWNER: 2025-04-21T20:25:12.8612615Z CHECK_EXPERIMENTS: 2025-04-21T20:25:12.8613031Z PR_NUMBER: 2025-04-21T20:25:12.8613387Z ##[endgroup] 2025-04-21T20:25:12.8889851Z # flake8: noqa: G004 2025-04-21T20:25:12.8890188Z 2025-04-21T20:25:12.8890609Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-04-21T20:25:12.8891534Z # must be kept in sync. You can do it easily by running the following command: 2025-04-21T20:25:12.8892305Z # python .github/scripts/update_runner_determinator.py 2025-04-21T20:25:12.8892728Z 2025-04-21T20:25:12.8892877Z """ 2025-04-21T20:25:12.8893435Z This runner determinator is used to determine which set of runners to run a 2025-04-21T20:25:12.8894265Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-04-21T20:25:12.8895475Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-04-21T20:25:12.8896245Z of which runners should be used to run which job. 2025-04-21T20:25:12.8896956Z 2025-04-21T20:25:12.8897321Z The configuration has two parts, the settings and a list of opted-in users, 2025-04-21T20:25:12.8898162Z separated by a line containing "---". If the line is not present, the 2025-04-21T20:25:12.8898969Z settings are considered to be empty with only the second part, the user 2025-04-21T20:25:12.8899620Z list, defined. 2025-04-21T20:25:12.8899826Z 2025-04-21T20:25:12.8900167Z The first part is a YAML block that defines the rollout settings. This can be 2025-04-21T20:25:12.8901033Z used to define any settings that are needed to determine which runners to use. 2025-04-21T20:25:12.8901802Z It's fields are defined by the RolloutSettings class below. 2025-04-21T20:25:12.8902221Z 2025-04-21T20:25:12.8902570Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-04-21T20:25:12.8903409Z The user list is also a comma separated list of additional features or 2025-04-21T20:25:12.8904105Z experiments which the user could be opted in to. 2025-04-21T20:25:12.8904854Z 2025-04-21T20:25:12.8905131Z The user list has the following rules: 2025-04-21T20:25:12.8905464Z 2025-04-21T20:25:12.8905759Z - Users are GitHub usernames, which must start with the @ prefix 2025-04-21T20:25:12.8906591Z - Each user is also a comma-separated list of features/experiments to enable 2025-04-21T20:25:12.8907305Z - A "#" prefix opts the user out of all experiments 2025-04-21T20:25:12.8907677Z 2025-04-21T20:25:12.8907835Z Example config: 2025-04-21T20:25:12.8908268Z # A list of experiments that can be opted into. 2025-04-21T20:25:12.8908893Z # This defines the behavior they'll induce when opted into. 2025-04-21T20:25:12.8909477Z # Expected syntax is: 2025-04-21T20:25:12.8910078Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-04-21T20:25:12.8911181Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-04-21T20:25:12.8911763Z 2025-04-21T20:25:12.8911928Z experiments: 2025-04-21T20:25:12.8912300Z lf: 2025-04-21T20:25:12.8912648Z rollout_percent: 25 2025-04-21T20:25:12.8913076Z all_branches: false 2025-04-21T20:25:12.8913490Z default: true 2025-04-21T20:25:12.8913866Z --- 2025-04-21T20:25:12.8914059Z 2025-04-21T20:25:12.8914209Z # Opt-ins: 2025-04-21T20:25:12.8915049Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-04-21T20:25:12.8915873Z # and specifying experiments to enable in a comma-separated list. 2025-04-21T20:25:12.8916601Z # To always opt out of an experiment, prefix it with a "-". 2025-04-21T20:25:12.8917344Z # Experiments should be from the above list. 2025-04-21T20:25:12.8917821Z 2025-04-21T20:25:12.8918099Z @User1,-lf,split_build 2025-04-21T20:25:12.8918508Z @User2,lf 2025-04-21T20:25:12.8918881Z @User3,split_build 2025-04-21T20:25:12.8919261Z """ 2025-04-21T20:25:12.8919443Z 2025-04-21T20:25:12.8919597Z import json 2025-04-21T20:25:12.8919941Z import logging 2025-04-21T20:25:12.8920299Z import os 2025-04-21T20:25:12.8920633Z import random 2025-04-21T20:25:12.8920986Z import re 2025-04-21T20:25:12.8921332Z import sys 2025-04-21T20:25:12.8921704Z from argparse import ArgumentParser 2025-04-21T20:25:12.8922197Z from collections.abc import Iterable 2025-04-21T20:25:12.8922680Z from functools import cache 2025-04-21T20:25:12.8923131Z from logging import LogRecord 2025-04-21T20:25:12.8923600Z from typing import Any, NamedTuple 2025-04-21T20:25:12.8924098Z from urllib.request import Request, urlopen 2025-04-21T20:25:12.8924802Z 2025-04-21T20:25:12.8924963Z import yaml 2025-04-21T20:25:12.8925331Z from github import Auth, Github 2025-04-21T20:25:12.8925779Z from github.Issue import Issue 2025-04-21T20:25:12.8926069Z 2025-04-21T20:25:12.8926075Z 2025-04-21T20:25:12.8926278Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-04-21T20:25:12.8926923Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-04-21T20:25:12.8927731Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-04-21T20:25:12.8928464Z 2025-04-21T20:25:12.8928682Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-04-21T20:25:12.8929218Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-04-21T20:25:12.8929690Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-04-21T20:25:12.8930207Z OPT_OUT_LABEL = "no-runner-experiments" 2025-04-21T20:25:12.8930534Z 2025-04-21T20:25:12.8930715Z SETTING_EXPERIMENTS = "experiments" 2025-04-21T20:25:12.8931028Z 2025-04-21T20:25:12.8931207Z LF_FLEET_EXPERIMENT = "lf" 2025-04-21T20:25:12.8931635Z CANARY_FLEET_SUFFIX = ".c" 2025-04-21T20:25:12.8931899Z 2025-04-21T20:25:12.8931905Z 2025-04-21T20:25:12.8932084Z class Experiment(NamedTuple): 2025-04-21T20:25:12.8932527Z rollout_perc: float = ( 2025-04-21T20:25:12.8933124Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-04-21T20:25:12.8933779Z ) 2025-04-21T20:25:12.8934127Z all_branches: bool = ( 2025-04-21T20:25:12.8934921Z False # If True, the experiment is also enabled on the exception branches 2025-04-21T20:25:12.8935567Z ) 2025-04-21T20:25:12.8935910Z default: bool = ( 2025-04-21T20:25:12.8936440Z True # If True, the experiment is enabled by default for all queries 2025-04-21T20:25:12.8937050Z ) 2025-04-21T20:25:12.8937238Z 2025-04-21T20:25:12.8937408Z # Add more fields as needed 2025-04-21T20:25:12.8937695Z 2025-04-21T20:25:12.8937700Z 2025-04-21T20:25:12.8937870Z class Settings(NamedTuple): 2025-04-21T20:25:12.8938286Z """ 2025-04-21T20:25:12.8938706Z Settings for the experiments that can be opted into. 2025-04-21T20:25:12.8939242Z """ 2025-04-21T20:25:12.8939425Z 2025-04-21T20:25:12.8939619Z experiments: dict[str, Experiment] = {} 2025-04-21T20:25:12.8939969Z 2025-04-21T20:25:12.8939974Z 2025-04-21T20:25:12.8940348Z class ColorFormatter(logging.Formatter): 2025-04-21T20:25:12.8940949Z """Color codes the log messages based on the log level""" 2025-04-21T20:25:12.8941359Z 2025-04-21T20:25:12.8941512Z COLORS = { 2025-04-21T20:25:12.8941887Z "WARNING": "\033[33m", # Yellow 2025-04-21T20:25:12.8942355Z "ERROR": "\033[31m", # Red 2025-04-21T20:25:12.8942813Z "CRITICAL": "\033[31m", # Red 2025-04-21T20:25:12.8943282Z "INFO": "\033[0m", # Reset 2025-04-21T20:25:12.8943747Z "DEBUG": "\033[0m", # Reset 2025-04-21T20:25:12.8944182Z } 2025-04-21T20:25:12.8944559Z 2025-04-21T20:25:12.8944767Z def format(self, record: LogRecord) -> str: 2025-04-21T20:25:12.8945469Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-04-21T20:25:12.8946196Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-04-21T20:25:12.8946739Z return super().format(record) 2025-04-21T20:25:12.8947045Z 2025-04-21T20:25:12.8947051Z 2025-04-21T20:25:12.8947239Z handler = logging.StreamHandler() 2025-04-21T20:25:12.8947893Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-04-21T20:25:12.8948420Z 2025-04-21T20:25:12.8948650Z log = logging.getLogger(os.path.basename(__file__)) 2025-04-21T20:25:12.8949193Z log.addHandler(handler) 2025-04-21T20:25:12.8949605Z log.setLevel(logging.INFO) 2025-04-21T20:25:12.8949875Z 2025-04-21T20:25:12.8949881Z 2025-04-21T20:25:12.8950112Z def set_github_output(key: str, value: str) -> None: 2025-04-21T20:25:12.8950634Z """ 2025-04-21T20:25:12.8951099Z Defines outputs of the github action that invokes this script 2025-04-21T20:25:12.8951686Z """ 2025-04-21T20:25:12.8952021Z if not GITHUB_OUTPUT: 2025-04-21T20:25:12.8953025Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-04-21T20:25:12.8954072Z log.warning( 2025-04-21T20:25:12.8955121Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-04-21T20:25:12.8956001Z ) 2025-04-21T20:25:12.8967055Z print(f"::set-output name={key}::{value}") 2025-04-21T20:25:12.8967874Z return 2025-04-21T20:25:12.8968092Z 2025-04-21T20:25:12.8968291Z with open(GITHUB_OUTPUT, "a") as f: 2025-04-21T20:25:12.8968839Z log.info(f"Setting output: {key}='{value}'") 2025-04-21T20:25:12.8969373Z f.write(f"{key}={value}\n") 2025-04-21T20:25:12.8969688Z 2025-04-21T20:25:12.8969694Z 2025-04-21T20:25:12.8969995Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-04-21T20:25:12.8970598Z return frozenset( 2025-04-21T20:25:12.8971178Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-04-21T20:25:12.8971820Z ) 2025-04-21T20:25:12.8972007Z 2025-04-21T20:25:12.8972013Z 2025-04-21T20:25:12.8972185Z def parse_args() -> Any: 2025-04-21T20:25:12.8972722Z parser = ArgumentParser("Get dynamic rollout settings") 2025-04-21T20:25:12.8973536Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-04-21T20:25:12.8974259Z parser.add_argument( 2025-04-21T20:25:12.8974941Z "--github-issue-repo", 2025-04-21T20:25:12.8975371Z type=str, 2025-04-21T20:25:12.8975743Z required=False, 2025-04-21T20:25:12.8976159Z default="pytorch/test-infra", 2025-04-21T20:25:12.8976662Z help="GitHub repo to get the issue", 2025-04-21T20:25:12.8977138Z ) 2025-04-21T20:25:12.8977478Z parser.add_argument( 2025-04-21T20:25:12.8977885Z "--github-repo", 2025-04-21T20:25:12.8978283Z type=str, 2025-04-21T20:25:12.8978648Z required=True, 2025-04-21T20:25:12.8979069Z help="GitHub repo where CI is running", 2025-04-21T20:25:12.8979555Z ) 2025-04-21T20:25:12.8979898Z parser.add_argument( 2025-04-21T20:25:12.8980457Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-04-21T20:25:12.8981211Z ) 2025-04-21T20:25:12.8981568Z parser.add_argument( 2025-04-21T20:25:12.8982143Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-04-21T20:25:12.8982777Z ) 2025-04-21T20:25:12.8983108Z parser.add_argument( 2025-04-21T20:25:12.8983701Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-04-21T20:25:12.8984506Z ) 2025-04-21T20:25:12.8984961Z parser.add_argument( 2025-04-21T20:25:12.8985592Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-04-21T20:25:12.8986253Z ) 2025-04-21T20:25:12.8986599Z parser.add_argument( 2025-04-21T20:25:12.8987010Z "--github-ref-type", 2025-04-21T20:25:12.8987437Z type=str, 2025-04-21T20:25:12.8987795Z required=True, 2025-04-21T20:25:12.8988242Z help="Current GitHub ref type, branch or tag", 2025-04-21T20:25:12.8988746Z ) 2025-04-21T20:25:12.8989084Z parser.add_argument( 2025-04-21T20:25:12.8989517Z "--eligible-experiments", 2025-04-21T20:25:12.8989993Z type=_str_comma_separated_to_set, 2025-04-21T20:25:12.8990482Z required=False, 2025-04-21T20:25:12.8990863Z default="", 2025-04-21T20:25:12.8991652Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-04-21T20:25:12.8992512Z ) 2025-04-21T20:25:12.8992850Z parser.add_argument( 2025-04-21T20:25:12.8993254Z "--pr-number", 2025-04-21T20:25:12.8993636Z type=str, 2025-04-21T20:25:12.8993997Z required=False, 2025-04-21T20:25:12.8994603Z default="", 2025-04-21T20:25:12.8995054Z help="the optional PR number where this is run", 2025-04-21T20:25:12.8995563Z ) 2025-04-21T20:25:12.8995742Z 2025-04-21T20:25:12.8995925Z return parser.parse_args() 2025-04-21T20:25:12.8996216Z 2025-04-21T20:25:12.8996222Z 2025-04-21T20:25:12.8996609Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-04-21T20:25:12.8997313Z auth = Auth.Token(github_token) 2025-04-21T20:25:12.8997781Z return Github(auth=auth) 2025-04-21T20:25:12.8998242Z 2025-04-21T20:25:12.8998248Z 2025-04-21T20:25:12.8998672Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-04-21T20:25:12.8999425Z repo = gh.get_repo(repo) 2025-04-21T20:25:12.8999885Z return repo.get_issue(number=issue_num) 2025-04-21T20:25:12.9000229Z 2025-04-21T20:25:12.9000236Z 2025-04-21T20:25:12.9000410Z def get_potential_pr_author( 2025-04-21T20:25:12.9001004Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-04-21T20:25:12.9001634Z ) -> str: 2025-04-21T20:25:12.9002113Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-04-21T20:25:12.9002871Z # Fetch the actual username from the original PR. The PR number is 2025-04-21T20:25:12.9003575Z # embedded in the tag name: ciflow// 2025-04-21T20:25:12.9003965Z 2025-04-21T20:25:12.9004137Z gh = get_gh_client(github_token) 2025-04-21T20:25:12.9004672Z 2025-04-21T20:25:12.9004936Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-04-21T20:25:12.9005515Z split_tag = ref_name.split("/") 2025-04-21T20:25:12.9005977Z if ( 2025-04-21T20:25:12.9006333Z len(split_tag) == 3 2025-04-21T20:25:12.9006786Z and split_tag[0] == "ciflow" 2025-04-21T20:25:12.9007274Z and split_tag[2].isnumeric() 2025-04-21T20:25:12.9007720Z ): 2025-04-21T20:25:12.9008077Z pr_number = split_tag[2] 2025-04-21T20:25:12.9008520Z try: 2025-04-21T20:25:12.9008918Z repository = gh.get_repo(repo) 2025-04-21T20:25:12.9009489Z pull = repository.get_pull(number=int(pr_number)) 2025-04-21T20:25:12.9010049Z except Exception as e: 2025-04-21T20:25:12.9010519Z raise Exception( # noqa: TRY002 2025-04-21T20:25:12.9011283Z f"issue with pull request {pr_number} from repo {repository}" 2025-04-21T20:25:12.9011894Z ) from e 2025-04-21T20:25:12.9012393Z return pull.user.login # type: ignore[no-any-return] 2025-04-21T20:25:12.9013040Z # In all other cases, return the original input username 2025-04-21T20:25:12.9013582Z return username 2025-04-21T20:25:12.9013816Z 2025-04-21T20:25:12.9013821Z 2025-04-21T20:25:12.9014034Z def is_exception_branch(branch: str) -> bool: 2025-04-21T20:25:12.9014756Z """ 2025-04-21T20:25:12.9015361Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-04-21T20:25:12.9016076Z """ 2025-04-21T20:25:12.9016580Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-04-21T20:25:12.9017060Z 2025-04-21T20:25:12.9017067Z 2025-04-21T20:25:12.9017253Z def load_yaml(yaml_text: str) -> Any: 2025-04-21T20:25:12.9017708Z try: 2025-04-21T20:25:12.9018068Z data = yaml.safe_load(yaml_text) 2025-04-21T20:25:12.9018535Z return data 2025-04-21T20:25:12.9018918Z except yaml.YAMLError: 2025-04-21T20:25:12.9019369Z log.exception("Error loading YAML") 2025-04-21T20:25:12.9019846Z raise 2025-04-21T20:25:12.9020044Z 2025-04-21T20:25:12.9020051Z 2025-04-21T20:25:12.9020441Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-04-21T20:25:12.9021119Z """ 2025-04-21T20:25:12.9021696Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-04-21T20:25:12.9022247Z 2025-04-21T20:25:12.9022573Z If the issue body contains "---" then the text above that is the settings 2025-04-21T20:25:12.9023290Z and the text below is the list of opted in users. 2025-04-21T20:25:12.9023677Z 2025-04-21T20:25:12.9024038Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-04-21T20:25:12.9024938Z """ 2025-04-21T20:25:12.9025397Z rollout_state_parts = rollout_state.split("---") 2025-04-21T20:25:12.9025967Z if len(rollout_state_parts) >= 2: 2025-04-21T20:25:12.9026519Z return rollout_state_parts[0], rollout_state_parts[1] 2025-04-21T20:25:12.9027232Z else: 2025-04-21T20:25:12.9027591Z return "", rollout_state 2025-04-21T20:25:12.9027880Z 2025-04-21T20:25:12.9027885Z 2025-04-21T20:25:12.9028069Z class UserOptins(dict[str, list[str]]): 2025-04-21T20:25:12.9028537Z """ 2025-04-21T20:25:12.9029016Z Dictionary of users with a list of features they have opted into 2025-04-21T20:25:12.9029612Z """ 2025-04-21T20:25:12.9029787Z 2025-04-21T20:25:12.9029794Z 2025-04-21T20:25:12.9030112Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-04-21T20:25:12.9030716Z """ 2025-04-21T20:25:12.9031381Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-04-21T20:25:12.9032020Z 2025-04-21T20:25:12.9032615Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-04-21T20:25:12.9033533Z - Example line: "@User1,lf,split_build" 2025-04-21T20:25:12.9034172Z - A "#" prefix indicates the user is opted out of all experiments 2025-04-21T20:25:12.9034749Z 2025-04-21T20:25:12.9034755Z 2025-04-21T20:25:12.9034898Z """ 2025-04-21T20:25:12.9035242Z optins = UserOptins() 2025-04-21T20:25:12.9035690Z for user in user_optin_text.split("\n"): 2025-04-21T20:25:12.9036200Z user = user.strip("\r\n\t -") 2025-04-21T20:25:12.9036695Z if not user or not user.startswith("@"): 2025-04-21T20:25:12.9037203Z # Not a valid user. Skip 2025-04-21T20:25:12.9037652Z continue 2025-04-21T20:25:12.9037877Z 2025-04-21T20:25:12.9038023Z if user: 2025-04-21T20:25:12.9038432Z usr_name = user.split(",")[0].strip("@") 2025-04-21T20:25:12.9039204Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-04-21T20:25:12.9039678Z 2025-04-21T20:25:12.9039828Z return optins 2025-04-21T20:25:12.9040060Z 2025-04-21T20:25:12.9040066Z 2025-04-21T20:25:12.9040343Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-04-21T20:25:12.9040895Z """ 2025-04-21T20:25:12.9041260Z Check if the experiment name is valid. 2025-04-21T20:25:12.9041731Z A valid name: 2025-04-21T20:25:12.9042319Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-04-21T20:25:12.9043176Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-04-21T20:25:12.9043834Z - Cannot contain spaces 2025-04-21T20:25:12.9044247Z """ 2025-04-21T20:25:12.9044550Z 2025-04-21T20:25:12.9044791Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-04-21T20:25:12.9045450Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-04-21T20:25:12.9045863Z 2025-04-21T20:25:12.9046010Z if valid: 2025-04-21T20:25:12.9046365Z return True 2025-04-21T20:25:12.9046585Z 2025-04-21T20:25:12.9046734Z log.error( 2025-04-21T20:25:12.9048086Z 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-04-21T20:25:12.9049633Z ) 2025-04-21T20:25:12.9049956Z return False 2025-04-21T20:25:12.9050180Z 2025-04-21T20:25:12.9050186Z 2025-04-21T20:25:12.9050473Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-04-21T20:25:12.9051041Z """ 2025-04-21T20:25:12.9051583Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-04-21T20:25:12.9103008Z """ 2025-04-21T20:25:12.9103891Z try: 2025-04-21T20:25:12.9104688Z if settings_text: 2025-04-21T20:25:12.9105870Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-04-21T20:25:12.9106760Z # for easy reading 2025-04-21T20:25:12.9107513Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-04-21T20:25:12.9108693Z # the backtick character in shell commands. 2025-04-21T20:25:12.9109259Z backtick = chr(96) # backtick character 2025-04-21T20:25:12.9109879Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-04-21T20:25:12.9110502Z settings = load_yaml(settings_text) 2025-04-21T20:25:12.9110851Z 2025-04-21T20:25:12.9111238Z # For now we just load experiments. We can expand this if/when we add more settings 2025-04-21T20:25:12.9111942Z experiments = {} 2025-04-21T20:25:12.9112223Z 2025-04-21T20:25:12.9112556Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-04-21T20:25:12.9113260Z if not is_valid_experiment_name(exp_name): 2025-04-21T20:25:12.9114290Z # 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-04-21T20:25:12.9115536Z continue 2025-04-21T20:25:12.9115807Z 2025-04-21T20:25:12.9115987Z valid_settings = {} 2025-04-21T20:25:12.9116465Z for setting in exp_settings: 2025-04-21T20:25:12.9116992Z if setting not in Experiment._fields: 2025-04-21T20:25:12.9117500Z log.warning( 2025-04-21T20:25:12.9118157Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-04-21T20:25:12.9118816Z ) 2025-04-21T20:25:12.9119215Z else: 2025-04-21T20:25:12.9119692Z valid_settings[setting] = exp_settings[setting] 2025-04-21T20:25:12.9120085Z 2025-04-21T20:25:12.9120339Z experiments[exp_name] = Experiment(**valid_settings) 2025-04-21T20:25:12.9121078Z return Settings(experiments) 2025-04-21T20:25:12.9121414Z 2025-04-21T20:25:12.9121573Z except Exception: 2025-04-21T20:25:12.9122011Z log.exception("Failed to parse settings") 2025-04-21T20:25:12.9122369Z 2025-04-21T20:25:12.9122528Z return Settings() 2025-04-21T20:25:12.9122778Z 2025-04-21T20:25:12.9122783Z 2025-04-21T20:25:12.9123015Z def parse_settings(rollout_state: str) -> Settings: 2025-04-21T20:25:12.9123546Z """ 2025-04-21T20:25:12.9123940Z Parse settings, if any, from the rollout state. 2025-04-21T20:25:12.9124503Z 2025-04-21T20:25:12.9124868Z If the issue body contains "---" then the text above that is the settings 2025-04-21T20:25:12.9125574Z and the text below is the list of opted in users. 2025-04-21T20:25:12.9125956Z 2025-04-21T20:25:12.9126365Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-04-21T20:25:12.9127048Z """ 2025-04-21T20:25:12.9127560Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-04-21T20:25:12.9128267Z return parse_settings_from_text(settings_text) 2025-04-21T20:25:12.9128637Z 2025-04-21T20:25:12.9128650Z 2025-04-21T20:25:12.9128880Z def parse_users(rollout_state: str) -> UserOptins: 2025-04-21T20:25:12.9129405Z """ 2025-04-21T20:25:12.9129763Z Parse users from the rollout state. 2025-04-21T20:25:12.9130095Z 2025-04-21T20:25:12.9130237Z """ 2025-04-21T20:25:12.9130730Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-04-21T20:25:12.9131405Z return parse_user_opt_in_from_text(users_text) 2025-04-21T20:25:12.9131771Z 2025-04-21T20:25:12.9131778Z 2025-04-21T20:25:12.9132170Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-04-21T20:25:12.9132854Z """ 2025-04-21T20:25:12.9133233Z Check if a user is opted into an experiment 2025-04-21T20:25:12.9133720Z """ 2025-04-21T20:25:12.9134135Z return experiment_name in user_optins.get(user, []) 2025-04-21T20:25:12.9134733Z 2025-04-21T20:25:12.9134741Z 2025-04-21T20:25:12.9135143Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-04-21T20:25:12.9136062Z """ 2025-04-21T20:25:12.9136488Z Check if a user explicitly opted out of an experiment 2025-04-21T20:25:12.9137015Z """ 2025-04-21T20:25:12.9137471Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-04-21T20:25:12.9138096Z experiment_optout = "-" + experiment_name 2025-04-21T20:25:12.9138681Z if experiment_optout not in user_optins.get(user, []): 2025-04-21T20:25:12.9139224Z return False 2025-04-21T20:25:12.9139461Z 2025-04-21T20:25:12.9139704Z if is_user_opted_in(user, user_optins, experiment_name): 2025-04-21T20:25:12.9140256Z log.warning( 2025-04-21T20:25:12.9140988Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-04-21T20:25:12.9141807Z ) 2025-04-21T20:25:12.9142001Z 2025-04-21T20:25:12.9142147Z return True 2025-04-21T20:25:12.9142367Z 2025-04-21T20:25:12.9142374Z 2025-04-21T20:25:12.9142539Z def get_runner_prefix( 2025-04-21T20:25:12.9142950Z rollout_state: str, 2025-04-21T20:25:12.9143371Z workflow_requestors: Iterable[str], 2025-04-21T20:25:12.9143849Z branch: str, 2025-04-21T20:25:12.9144300Z eligible_experiments: frozenset[str] = frozenset(), 2025-04-21T20:25:12.9145147Z is_canary: bool = False, 2025-04-21T20:25:12.9145568Z ) -> str: 2025-04-21T20:25:12.9145949Z settings = parse_settings(rollout_state) 2025-04-21T20:25:12.9146473Z user_optins = parse_users(rollout_state) 2025-04-21T20:25:12.9146820Z 2025-04-21T20:25:12.9146981Z fleet_prefix = "" 2025-04-21T20:25:12.9147362Z prefixes = [] 2025-04-21T20:25:12.9147936Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-04-21T20:25:12.9148807Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-04-21T20:25:12.9149613Z log.info( 2025-04-21T20:25:12.9150252Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-04-21T20:25:12.9150952Z ) 2025-04-21T20:25:12.9151297Z continue 2025-04-21T20:25:12.9151521Z 2025-04-21T20:25:12.9151692Z if eligible_experiments: 2025-04-21T20:25:12.9152206Z if experiment_name not in eligible_experiments: 2025-04-21T20:25:12.9152792Z exp_list = ", ".join(eligible_experiments) 2025-04-21T20:25:12.9153299Z log.info( 2025-04-21T20:25:12.9154055Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-04-21T20:25:12.9155546Z ) 2025-04-21T20:25:12.9155918Z continue 2025-04-21T20:25:12.9156346Z elif not experiment_settings.default: 2025-04-21T20:25:12.9156840Z log.info( 2025-04-21T20:25:12.9157440Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-04-21T20:25:12.9158123Z ) 2025-04-21T20:25:12.9158472Z continue 2025-04-21T20:25:12.9158705Z 2025-04-21T20:25:12.9158956Z # Is any workflow_requestor opted out to this experiment? 2025-04-21T20:25:12.9159569Z opted_out_users = [ 2025-04-21T20:25:12.9159972Z requestor 2025-04-21T20:25:12.9160388Z for requestor in workflow_requestors 2025-04-21T20:25:12.9160999Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-04-21T20:25:12.9161578Z ] 2025-04-21T20:25:12.9161762Z 2025-04-21T20:25:12.9161934Z if opted_out_users: 2025-04-21T20:25:12.9162338Z log.info( 2025-04-21T20:25:12.9162907Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-04-21T20:25:12.9163547Z ) 2025-04-21T20:25:12.9163896Z continue 2025-04-21T20:25:12.9164122Z 2025-04-21T20:25:12.9164583Z # Is any workflow_requestor opted in to this experiment? 2025-04-21T20:25:12.9165311Z opted_in_users = [ 2025-04-21T20:25:12.9165714Z requestor 2025-04-21T20:25:12.9166322Z for requestor in workflow_requestors 2025-04-21T20:25:12.9166952Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-04-21T20:25:12.9167519Z ] 2025-04-21T20:25:12.9167707Z 2025-04-21T20:25:12.9167867Z enabled = False 2025-04-21T20:25:12.9168265Z if opted_in_users: 2025-04-21T20:25:12.9168669Z log.info( 2025-04-21T20:25:12.9169208Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-04-21T20:25:12.9169834Z ) 2025-04-21T20:25:12.9170178Z enabled = True 2025-04-21T20:25:12.9170435Z 2025-04-21T20:25:12.9170631Z elif experiment_settings.rollout_perc: 2025-04-21T20:25:12.9171394Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-04-21T20:25:12.9172250Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-04-21T20:25:12.9172854Z log.info( 2025-04-21T20:25:12.9173653Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-04-21T20:25:12.9174827Z ) 2025-04-21T20:25:12.9175227Z enabled = True 2025-04-21T20:25:12.9175511Z 2025-04-21T20:25:12.9175661Z if enabled: 2025-04-21T20:25:12.9176056Z label = experiment_name 2025-04-21T20:25:12.9176554Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-04-21T20:25:12.9177317Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-04-21T20:25:12.9178118Z # - If it's enabled, then we always list it's prefix first 2025-04-21T20:25:12.9178956Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-04-21T20:25:12.9179575Z if is_canary: 2025-04-21T20:25:12.9180031Z label += CANARY_FLEET_SUFFIX 2025-04-21T20:25:12.9180539Z fleet_prefix = label 2025-04-21T20:25:12.9180982Z else: 2025-04-21T20:25:12.9181376Z prefixes.append(label) 2025-04-21T20:25:12.9181696Z 2025-04-21T20:25:12.9181861Z if len(prefixes) > 1: 2025-04-21T20:25:12.9182271Z log.error( 2025-04-21T20:25:12.9183225Z 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-04-21T20:25:12.9184261Z ) 2025-04-21T20:25:12.9184753Z prefixes = prefixes[:1] 2025-04-21T20:25:12.9185045Z 2025-04-21T20:25:12.9185220Z # Fleet always comes first 2025-04-21T20:25:12.9185650Z if fleet_prefix: 2025-04-21T20:25:12.9186059Z prefixes.insert(0, fleet_prefix) 2025-04-21T20:25:12.9186384Z 2025-04-21T20:25:12.9186626Z return ".".join(prefixes) + "." if prefixes else "" 2025-04-21T20:25:12.9187002Z 2025-04-21T20:25:12.9187008Z 2025-04-21T20:25:12.9187417Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-04-21T20:25:12.9188143Z """ 2025-04-21T20:25:12.9188683Z Gets the first comment of the issue, which contains the desired rollout state. 2025-04-21T20:25:12.9189207Z 2025-04-21T20:25:12.9189562Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-04-21T20:25:12.9190207Z """ 2025-04-21T20:25:12.9190554Z gh = get_gh_client(github_token) 2025-04-21T20:25:12.9191050Z issue = get_issue(gh, repo, issue_num) 2025-04-21T20:25:12.9191624Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-04-21T20:25:12.9192033Z 2025-04-21T20:25:12.9192039Z 2025-04-21T20:25:12.9192408Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-04-21T20:25:12.9193103Z for _ in range(num_retries): 2025-04-21T20:25:12.9193526Z try: 2025-04-21T20:25:12.9193913Z req = Request(url=url, headers=headers) 2025-04-21T20:25:12.9194629Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-04-21T20:25:12.9195373Z return json.loads(content) 2025-04-21T20:25:12.9195849Z except Exception as e: 2025-04-21T20:25:12.9196345Z log.warning(f"Could not download {url}: {e}") 2025-04-21T20:25:12.9196714Z 2025-04-21T20:25:12.9197063Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-04-21T20:25:12.9197718Z return {} 2025-04-21T20:25:12.9197919Z 2025-04-21T20:25:12.9197925Z 2025-04-21T20:25:12.9198078Z @cache 2025-04-21T20:25:12.9198639Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-04-21T20:25:12.9199330Z """ 2025-04-21T20:25:12.9199685Z Dynamically get PR information 2025-04-21T20:25:12.9200131Z """ 2025-04-21T20:25:12.9200577Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-04-21T20:25:12.9201154Z headers = { 2025-04-21T20:25:12.9201563Z "Accept": "application/vnd.github.v3+json", 2025-04-21T20:25:12.9202121Z "Authorization": f"token {github_token}", 2025-04-21T20:25:12.9202608Z } 2025-04-21T20:25:12.9202992Z json_response: dict[str, Any] = download_json( 2025-04-21T20:25:12.9203539Z url=f"{github_api}/issues/{pr_number}", 2025-04-21T20:25:12.9204047Z headers=headers, 2025-04-21T20:25:12.9204545Z ) 2025-04-21T20:25:12.9204729Z 2025-04-21T20:25:12.9204893Z if not json_response: 2025-04-21T20:25:12.9205417Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-04-21T20:25:12.9205974Z return {} 2025-04-21T20:25:12.9206196Z 2025-04-21T20:25:12.9206353Z return json_response 2025-04-21T20:25:12.9206606Z 2025-04-21T20:25:12.9206612Z 2025-04-21T20:25:12.9206988Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-04-21T20:25:12.9207767Z """ 2025-04-21T20:25:12.9208252Z Dynamically get the latest list of labels from the pull request 2025-04-21T20:25:12.9208845Z """ 2025-04-21T20:25:12.9209289Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-04-21T20:25:12.9209843Z return { 2025-04-21T20:25:12.9210377Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-04-21T20:25:12.9211022Z } 2025-04-21T20:25:12.9211208Z 2025-04-21T20:25:12.9211215Z 2025-04-21T20:25:12.9211370Z def main() -> None: 2025-04-21T20:25:12.9211754Z args = parse_args() 2025-04-21T20:25:12.9212000Z 2025-04-21T20:25:12.9212202Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-04-21T20:25:12.9212563Z 2025-04-21T20:25:12.9212734Z # Check if the PR is opt-out 2025-04-21T20:25:12.9213172Z if args.pr_number: 2025-04-21T20:25:12.9213777Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-04-21T20:25:12.9214775Z if OPT_OUT_LABEL in labels: 2025-04-21T20:25:12.9215273Z log.info( 2025-04-21T20:25:12.9215928Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-04-21T20:25:12.9216634Z ) 2025-04-21T20:25:12.9217137Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-04-21T20:25:12.9217746Z sys.exit() 2025-04-21T20:25:12.9217984Z 2025-04-21T20:25:12.9218129Z try: 2025-04-21T20:25:12.9218522Z rollout_state = get_rollout_state_from_issue( 2025-04-21T20:25:12.9219166Z args.github_token, args.github_issue_repo, args.github_issue 2025-04-21T20:25:12.9219752Z ) 2025-04-21T20:25:12.9219940Z 2025-04-21T20:25:12.9220125Z username = get_potential_pr_author( 2025-04-21T20:25:12.9220626Z args.github_token, 2025-04-21T20:25:12.9221056Z args.github_repo, 2025-04-21T20:25:12.9221493Z args.github_actor, 2025-04-21T20:25:12.9221928Z args.github_ref_type, 2025-04-21T20:25:12.9222381Z args.github_branch, 2025-04-21T20:25:12.9222796Z ) 2025-04-21T20:25:12.9222980Z 2025-04-21T20:25:12.9223428Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-04-21T20:25:12.9223847Z 2025-04-21T20:25:12.9224047Z runner_label_prefix = get_runner_prefix( 2025-04-21T20:25:12.9224789Z rollout_state, 2025-04-21T20:25:12.9225243Z (args.github_issue_owner, username), 2025-04-21T20:25:12.9225745Z args.github_branch, 2025-04-21T20:25:12.9226192Z args.eligible_experiments, 2025-04-21T20:25:12.9226651Z is_canary, 2025-04-21T20:25:12.9227036Z ) 2025-04-21T20:25:12.9227223Z 2025-04-21T20:25:12.9227394Z except Exception as e: 2025-04-21T20:25:12.9227800Z log.error( 2025-04-21T20:25:12.9228408Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-04-21T20:25:12.9229096Z ) 2025-04-21T20:25:12.9229287Z 2025-04-21T20:25:12.9229595Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-04-21T20:25:12.9230049Z 2025-04-21T20:25:12.9230055Z 2025-04-21T20:25:12.9230220Z if __name__ == "__main__": 2025-04-21T20:25:12.9230625Z main() 2025-04-21T20:25:12.9230817Z 2025-04-21T20:25:12.9322214Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-04-21T20:25:12.9323086Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-04-21T20:25:12.9370578Z shell: /usr/bin/bash -e {0} 2025-04-21T20:25:12.9371063Z env: 2025-04-21T20:25:12.9371691Z GITHUB_TOKEN: *** 2025-04-21T20:25:12.9372096Z ISSUE_NUMBER: 5132 2025-04-21T20:25:12.9372525Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-04-21T20:25:12.9373000Z ISSUE_OWNER: 2025-04-21T20:25:12.9373377Z CHECK_EXPERIMENTS: 2025-04-21T20:25:12.9373771Z PR_NUMBER: 2025-04-21T20:25:12.9374136Z ##[endgroup] 2025-04-21T20:25:15.0000759Z Defaulting to user installation because normal site-packages is not writeable 2025-04-21T20:25:17.1103891Z Collecting urllib3==1.26.18 2025-04-21T20:25:17.1736413Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-04-21T20:25:17.1970563Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 3.2 MB/s eta 0:00:00 2025-04-21T20:25:17.2300096Z Collecting PyGithub==2.3.0 2025-04-21T20:25:17.2418413Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-04-21T20:25:17.2935946Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-04-21T20:25:17.3083782Z 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-04-21T20:25:17.3136140Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-04-21T20:25:17.3155909Z 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-04-21T20:25:17.3171937Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-04-21T20:25:17.3509526Z Collecting Deprecated (from PyGithub==2.3.0) 2025-04-21T20:25:17.3602186Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-04-21T20:25:17.3845823Z 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-04-21T20:25:17.5130906Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-04-21T20:25:17.5221492Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-04-21T20:25:17.6384896Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-04-21T20:25:17.6475401Z 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-04-21T20:25:17.6797320Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-04-21T20:25:17.6885807Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-04-21T20:25:17.7185072Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-04-21T20:25:17.7309723Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 12.9 MB/s eta 0:00:00 2025-04-21T20:25:17.7403282Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-04-21T20:25:17.7521349Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 35.1 MB/s eta 0:00:00 2025-04-21T20:25:17.7615161Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-04-21T20:25:17.7749806Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 74.8 MB/s eta 0:00:00 2025-04-21T20:25:17.7839905Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-04-21T20:25:17.7975285Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-04-21T20:25:17.8052433Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 82.4 MB/s eta 0:00:00 2025-04-21T20:25:17.8191148Z 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-04-21T20:25:17.8255286Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 17.5 MB/s eta 0:00:00 2025-04-21T20:25:17.8344844Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-04-21T20:25:17.8411241Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 22.8 MB/s eta 0:00:00 2025-04-21T20:25:18.1433123Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-04-21T20:25:18.6927686Z 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-04-21T20:25:18.7965246Z ##[group]Run curr_branch="ciflow/inductor/148893" 2025-04-21T20:25:18.7965667Z curr_branch="ciflow/inductor/148893" 2025-04-21T20:25:18.7965929Z curr_ref_type="tag" 2025-04-21T20:25:18.7966185Z echo "Current branch is '$curr_branch'" 2025-04-21T20:25:18.7966432Z  2025-04-21T20:25:18.7966614Z python3 runner_determinator.py \ 2025-04-21T20:25:18.7966899Z  --github-token "$GITHUB_TOKEN" \ 2025-04-21T20:25:18.7967182Z  --github-issue "$ISSUE_NUMBER" \ 2025-04-21T20:25:18.7967444Z  --github-branch "$curr_branch" \ 2025-04-21T20:25:18.7967701Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-04-21T20:25:18.7967977Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-04-21T20:25:18.7968257Z  --github-ref-type "$curr_ref_type" \ 2025-04-21T20:25:18.7968540Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-04-21T20:25:18.7968877Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-04-21T20:25:18.7969170Z  --pr-number "${PR_NUMBER}" 2025-04-21T20:25:18.8017307Z shell: /usr/bin/bash -e {0} 2025-04-21T20:25:18.8017551Z env: 2025-04-21T20:25:18.8018117Z GITHUB_TOKEN: *** 2025-04-21T20:25:18.8018311Z ISSUE_NUMBER: 5132 2025-04-21T20:25:18.8018523Z TRIGGERING_ACTOR: pytorch-bot[bot] 2025-04-21T20:25:18.8018755Z ISSUE_OWNER: 2025-04-21T20:25:18.8018947Z CHECK_EXPERIMENTS: 2025-04-21T20:25:18.8019156Z PR_NUMBER: 2025-04-21T20:25:18.8019329Z ##[endgroup] 2025-04-21T20:25:18.8090128Z Current branch is 'ciflow/inductor/148893' 2025-04-21T20:25:21.5387173Z INFO : Based on rollout percentage of 30%, enabling experiment ephemeral. 2025-04-21T20:25:21.5388245Z INFO : Setting output: label-type='ephemeral.' 2025-04-21T20:25:21.5758212Z Evaluate and set job outputs 2025-04-21T20:25:21.5765616Z Set output 'label-type' 2025-04-21T20:25:21.5767583Z Cleaning up orphan processes