2025-06-01T20:56:26.6337223Z Current runner version: '2.324.0' 2025-06-01T20:56:26.6363381Z ##[group]Operating System 2025-06-01T20:56:26.6364242Z Ubuntu 2025-06-01T20:56:26.6364915Z 24.04.2 2025-06-01T20:56:26.6365370Z LTS 2025-06-01T20:56:26.6365903Z ##[endgroup] 2025-06-01T20:56:26.6366404Z ##[group]Runner Image 2025-06-01T20:56:26.6367015Z Image: ubuntu-24.04 2025-06-01T20:56:26.6367507Z Version: 20250527.1.0 2025-06-01T20:56:26.6368610Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20250527.1/images/ubuntu/Ubuntu2404-Readme.md 2025-06-01T20:56:26.6369948Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20250527.1 2025-06-01T20:56:26.6370838Z ##[endgroup] 2025-06-01T20:56:26.6371411Z ##[group]Runner Image Provisioner 2025-06-01T20:56:26.6371995Z 2.0.437.1 2025-06-01T20:56:26.6372455Z ##[endgroup] 2025-06-01T20:56:26.6373354Z ##[group]GITHUB_TOKEN Permissions 2025-06-01T20:56:26.6375619Z Metadata: read 2025-06-01T20:56:26.6376246Z ##[endgroup] 2025-06-01T20:56:26.6378410Z Secret source: Actions 2025-06-01T20:56:26.6379572Z Prepare workflow directory 2025-06-01T20:56:26.6905775Z Prepare all required actions 2025-06-01T20:56:26.6960741Z Complete job name: get-label-type / runner-determinator 2025-06-01T20:56:27.4050542Z ##[group]Run cat < runner_determinator.py 2025-06-01T20:56:27.4052979Z cat < runner_determinator.py 2025-06-01T20:56:27.4053661Z # flake8: noqa: G004 2025-06-01T20:56:27.4054167Z  2025-06-01T20:56:27.4055208Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:27.4056374Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:27.4057318Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:27.4058113Z  2025-06-01T20:56:27.4058540Z """ 2025-06-01T20:56:27.4059248Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:27.4060311Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:27.4061509Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:27.4062459Z of which runners should be used to run which job. 2025-06-01T20:56:27.4063223Z  2025-06-01T20:56:27.4063949Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:27.4065103Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:27.4066199Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:27.4067044Z list, defined. 2025-06-01T20:56:27.4067547Z  2025-06-01T20:56:27.4068286Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:27.4069350Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:27.4070365Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:27.4071127Z  2025-06-01T20:56:27.4071849Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:27.4072855Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:27.4073801Z experiments which the user could be opted in to. 2025-06-01T20:56:27.4074497Z  2025-06-01T20:56:27.4075069Z The user list has the following rules: 2025-06-01T20:56:27.4075750Z  2025-06-01T20:56:27.4076379Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:27.4077373Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:27.4078330Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:27.4078966Z  2025-06-01T20:56:27.4079516Z Example config: 2025-06-01T20:56:27.4080393Z  # A list of experiments that can be opted into. 2025-06-01T20:56:27.4081266Z  # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:27.4082031Z  # Expected syntax is: 2025-06-01T20:56:27.4082838Z  # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:27.4083969Z  # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:27.4085144Z  2025-06-01T20:56:27.4085662Z  experiments: 2025-06-01T20:56:27.4086230Z  lf: 2025-06-01T20:56:27.4086702Z  rollout_percent: 25 2025-06-01T20:56:27.4087346Z  all_branches: false 2025-06-01T20:56:27.4087920Z  default: true 2025-06-01T20:56:27.4088461Z  --- 2025-06-01T20:56:27.4088876Z  2025-06-01T20:56:27.4089419Z  # Opt-ins: 2025-06-01T20:56:27.4090168Z  # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:27.4091299Z  # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:27.4092313Z  # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:27.4093090Z  # Experiments should be from the above list. 2025-06-01T20:56:27.4093760Z  2025-06-01T20:56:27.4094265Z  @User1,-lf,split_build 2025-06-01T20:56:27.4095123Z  @User2,lf 2025-06-01T20:56:27.4095686Z  @User3,split_build 2025-06-01T20:56:27.4096295Z """ 2025-06-01T20:56:27.4096746Z  2025-06-01T20:56:27.4097184Z import json 2025-06-01T20:56:27.4097733Z import logging 2025-06-01T20:56:27.4098215Z import os 2025-06-01T20:56:27.4098713Z import random 2025-06-01T20:56:27.4099226Z import re 2025-06-01T20:56:27.4099755Z import sys 2025-06-01T20:56:27.4100334Z from argparse import ArgumentParser 2025-06-01T20:56:27.4173978Z from collections.abc import Iterable 2025-06-01T20:56:27.4174969Z from functools import cache 2025-06-01T20:56:27.4175656Z from logging import LogRecord 2025-06-01T20:56:27.4176232Z from typing import Any, NamedTuple 2025-06-01T20:56:27.4176858Z from urllib.request import Request, urlopen 2025-06-01T20:56:27.4177431Z  2025-06-01T20:56:27.4177804Z import yaml 2025-06-01T20:56:27.4178295Z from github import Auth, Github 2025-06-01T20:56:27.4178854Z from github.Issue import Issue 2025-06-01T20:56:27.4179359Z  2025-06-01T20:56:27.4179710Z  2025-06-01T20:56:27.4180155Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:27.4180913Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:27.4181846Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:27.4182596Z  2025-06-01T20:56:27.4183047Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:27.4183664Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:27.4184254Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:27.4185144Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:27.4185697Z  2025-06-01T20:56:27.4186108Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:27.4186637Z  2025-06-01T20:56:27.4187076Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:27.4187710Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:27.4188197Z  2025-06-01T20:56:27.4188543Z  2025-06-01T20:56:27.4188929Z class Experiment(NamedTuple): 2025-06-01T20:56:27.4189478Z  rollout_perc: float = ( 2025-06-01T20:56:27.4190201Z  0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:27.4191164Z  ) 2025-06-01T20:56:27.4191563Z  all_branches: bool = ( 2025-06-01T20:56:27.4192275Z  False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:27.4193050Z  ) 2025-06-01T20:56:27.4193443Z  default: bool = ( 2025-06-01T20:56:27.4194091Z  True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:27.4194901Z  ) 2025-06-01T20:56:27.4195284Z  2025-06-01T20:56:27.4195674Z  # Add more fields as needed 2025-06-01T20:56:27.4196191Z  2025-06-01T20:56:27.4196536Z  2025-06-01T20:56:27.4196918Z class Settings(NamedTuple): 2025-06-01T20:56:27.4197420Z  """ 2025-06-01T20:56:27.4198032Z  Settings for the experiments that can be opted into. 2025-06-01T20:56:27.4198771Z  """ 2025-06-01T20:56:27.4199156Z  2025-06-01T20:56:27.4199586Z  experiments: dict[str, Experiment] = {} 2025-06-01T20:56:27.4200122Z  2025-06-01T20:56:27.4200629Z  2025-06-01T20:56:27.4201068Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:27.4201762Z  """Color codes the log messages based on the log level""" 2025-06-01T20:56:27.4202383Z  2025-06-01T20:56:27.4202748Z  COLORS = { 2025-06-01T20:56:27.4203216Z  "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:27.4203780Z  "ERROR": "\033[31m", # Red 2025-06-01T20:56:27.4204327Z  "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:27.4204995Z  "INFO": "\033[0m", # Reset 2025-06-01T20:56:27.4205542Z  "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:27.4206051Z  } 2025-06-01T20:56:27.4206416Z  2025-06-01T20:56:27.4206848Z  def format(self, record: LogRecord) -> str: 2025-06-01T20:56:27.4207654Z  log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:27.4208501Z  record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:27.4209143Z  return super().format(record) 2025-06-01T20:56:27.4209662Z  2025-06-01T20:56:27.4210011Z  2025-06-01T20:56:27.4210411Z handler = logging.StreamHandler() 2025-06-01T20:56:27.4211210Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:27.4211972Z  2025-06-01T20:56:27.4212447Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:27.4213081Z log.addHandler(handler) 2025-06-01T20:56:27.4213604Z log.setLevel(logging.INFO) 2025-06-01T20:56:27.4214093Z  2025-06-01T20:56:27.4214431Z  2025-06-01T20:56:27.4215133Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:27.4215750Z  """ 2025-06-01T20:56:27.4216309Z  Defines outputs of the github action that invokes this script 2025-06-01T20:56:27.4216983Z  """ 2025-06-01T20:56:27.4217387Z  if not GITHUB_OUTPUT: 2025-06-01T20:56:27.4218488Z  # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:27.4219664Z  log.warning( 2025-06-01T20:56:27.4220556Z  "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-06-01T20:56:27.4221526Z  ) 2025-06-01T20:56:27.4222001Z  print(f"::set-output name={key}::{value}") 2025-06-01T20:56:27.4222573Z  return 2025-06-01T20:56:27.4222980Z  2025-06-01T20:56:27.4223387Z  with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:27.4224153Z  log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:27.4224867Z  f.write(f"{key}={value}\n") 2025-06-01T20:56:27.4225394Z  2025-06-01T20:56:27.4225744Z  2025-06-01T20:56:27.4226276Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:27.4226973Z  return frozenset( 2025-06-01T20:56:27.4227639Z  filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:27.4228356Z  ) 2025-06-01T20:56:27.4228724Z  2025-06-01T20:56:27.4229064Z  2025-06-01T20:56:27.4229436Z def parse_args() -> Any: 2025-06-01T20:56:27.4230058Z  parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:27.4231012Z  parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:27.4231825Z  parser.add_argument( 2025-06-01T20:56:27.4232353Z  "--github-issue-repo", 2025-06-01T20:56:27.4232873Z  type=str, 2025-06-01T20:56:27.4233345Z  required=False, 2025-06-01T20:56:27.4233984Z  default="pytorch/test-infra", 2025-06-01T20:56:27.4234689Z  help="GitHub repo to get the issue", 2025-06-01T20:56:27.4235242Z  ) 2025-06-01T20:56:27.4235649Z  parser.add_argument( 2025-06-01T20:56:27.4236150Z  "--github-repo", 2025-06-01T20:56:27.4236630Z  type=str, 2025-06-01T20:56:27.4237088Z  required=True, 2025-06-01T20:56:27.4237625Z  help="GitHub repo where CI is running", 2025-06-01T20:56:27.4238178Z  ) 2025-06-01T20:56:27.4238581Z  parser.add_argument( 2025-06-01T20:56:27.4239245Z  "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:27.4239940Z  ) 2025-06-01T20:56:27.4240343Z  parser.add_argument( 2025-06-01T20:56:27.4241033Z  "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:27.4241751Z  ) 2025-06-01T20:56:27.4242148Z  parser.add_argument( 2025-06-01T20:56:27.4242847Z  "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:27.4243572Z  ) 2025-06-01T20:56:27.4243972Z  parser.add_argument( 2025-06-01T20:56:27.4244785Z  "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:27.4245532Z  ) 2025-06-01T20:56:27.4245929Z  parser.add_argument( 2025-06-01T20:56:27.4246431Z  "--github-ref-type", 2025-06-01T20:56:27.4246934Z  type=str, 2025-06-01T20:56:27.4247390Z  required=True, 2025-06-01T20:56:27.4247959Z  help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:27.4248546Z  ) 2025-06-01T20:56:27.4248948Z  parser.add_argument( 2025-06-01T20:56:27.4249462Z  "--eligible-experiments", 2025-06-01T20:56:27.4250055Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:27.4250613Z  required=False, 2025-06-01T20:56:27.4251091Z  default="", 2025-06-01T20:56:27.4251991Z  help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:27.4253006Z  ) 2025-06-01T20:56:27.4253420Z  parser.add_argument( 2025-06-01T20:56:27.4253935Z  "--opt-out-experiments", 2025-06-01T20:56:27.4254505Z  type=_str_comma_separated_to_set, 2025-06-01T20:56:27.4255296Z  required=False, 2025-06-01T20:56:27.4255777Z  default="", 2025-06-01T20:56:27.4256229Z  help=( 2025-06-01T20:56:27.4257080Z  "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:27.4258254Z  "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:27.4259129Z  ), 2025-06-01T20:56:27.4259519Z  ) 2025-06-01T20:56:27.4259927Z  parser.add_argument( 2025-06-01T20:56:27.4260431Z  "--pr-number", 2025-06-01T20:56:27.4260910Z  type=str, 2025-06-01T20:56:27.4261361Z  required=False, 2025-06-01T20:56:27.4261840Z  default="", 2025-06-01T20:56:27.4262383Z  help="the optional PR number where this is run", 2025-06-01T20:56:27.4262961Z  ) 2025-06-01T20:56:27.4263329Z  2025-06-01T20:56:27.4263718Z  return parser.parse_args() 2025-06-01T20:56:27.4264219Z  2025-06-01T20:56:27.4264564Z  2025-06-01T20:56:27.4265514Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.4266444Z  auth = Auth.Token(github_token) 2025-06-01T20:56:27.4267005Z  return Github(auth=auth) 2025-06-01T20:56:27.4267495Z  2025-06-01T20:56:27.4267834Z  2025-06-01T20:56:27.4268500Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.4269341Z  repo = gh.get_repo(repo) 2025-06-01T20:56:27.4269899Z  return repo.get_issue(number=issue_num) 2025-06-01T20:56:27.4270446Z  2025-06-01T20:56:27.4270787Z  2025-06-01T20:56:27.4271165Z def get_potential_pr_author( 2025-06-01T20:56:27.4271861Z  github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:27.4272576Z ) -> str: 2025-06-01T20:56:27.4273134Z  # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:27.4273985Z  # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:27.4274900Z  # embedded in the tag name: ciflow// 2025-06-01T20:56:27.4275507Z  2025-06-01T20:56:27.4275901Z  gh = get_gh_client(github_token) 2025-06-01T20:56:27.4276418Z  2025-06-01T20:56:27.4276902Z  if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:27.4277568Z  split_tag = ref_name.split("/") 2025-06-01T20:56:27.4278096Z  if ( 2025-06-01T20:56:27.4278526Z  len(split_tag) == 3 2025-06-01T20:56:27.4279055Z  and split_tag[0] == "ciflow" 2025-06-01T20:56:27.4279627Z  and split_tag[2].isnumeric() 2025-06-01T20:56:27.4280147Z  ): 2025-06-01T20:56:27.4280589Z  pr_number = split_tag[2] 2025-06-01T20:56:27.4281108Z  try: 2025-06-01T20:56:27.4281594Z  repository = gh.get_repo(repo) 2025-06-01T20:56:27.4282255Z  pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:27.4282911Z  except Exception as e: 2025-06-01T20:56:27.4283486Z  raise Exception( # noqa: TRY002 2025-06-01T20:56:27.4284205Z  f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:27.4284989Z  ) from e 2025-06-01T20:56:27.4285592Z  return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:27.4286350Z  # In all other cases, return the original input username 2025-06-01T20:56:27.4286981Z  return username 2025-06-01T20:56:27.4287419Z  2025-06-01T20:56:27.4287774Z  2025-06-01T20:56:27.4288209Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:27.4288903Z  """ 2025-06-01T20:56:27.4289585Z  Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:27.4290399Z  """ 2025-06-01T20:56:27.4290984Z  return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:27.4291685Z  2025-06-01T20:56:27.4292036Z  2025-06-01T20:56:27.4292435Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:27.4292973Z  try: 2025-06-01T20:56:27.4293394Z  data = yaml.safe_load(yaml_text) 2025-06-01T20:56:27.4293938Z  return data 2025-06-01T20:56:27.4294418Z  except yaml.YAMLError: 2025-06-01T20:56:27.4295209Z  log.exception("Error loading YAML") 2025-06-01T20:56:27.4295760Z  raise 2025-06-01T20:56:27.4296165Z  2025-06-01T20:56:27.4296509Z  2025-06-01T20:56:27.4297151Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:27.4297938Z  """ 2025-06-01T20:56:27.4298721Z  Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:27.4299523Z  2025-06-01T20:56:27.4300075Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.4300881Z  and the text below is the list of opted in users. 2025-06-01T20:56:27.4301465Z  2025-06-01T20:56:27.4302043Z  If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:27.4302785Z  """ 2025-06-01T20:56:27.4303264Z  rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:27.4303894Z  if len(rollout_state_parts) >= 2: 2025-06-01T20:56:27.4304549Z  return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:27.4305430Z  else: 2025-06-01T20:56:27.4305927Z  return "", rollout_state 2025-06-01T20:56:27.4306414Z  2025-06-01T20:56:27.4306756Z  2025-06-01T20:56:27.4307166Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:27.4307714Z  """ 2025-06-01T20:56:27.4308275Z  Dictionary of users with a list of features they have opted into 2025-06-01T20:56:27.4308949Z  """ 2025-06-01T20:56:27.4309327Z  2025-06-01T20:56:27.4309666Z  2025-06-01T20:56:27.4310216Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:27.4310908Z  """ 2025-06-01T20:56:27.4311639Z  Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-06-01T20:56:27.4312506Z  2025-06-01T20:56:27.4313304Z  Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:27.4314366Z  - Example line: "@User1,lf,split_build" 2025-06-01T20:56:27.4315196Z  - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:27.4315858Z  2025-06-01T20:56:27.4316192Z  2025-06-01T20:56:27.4316534Z  """ 2025-06-01T20:56:27.4316925Z  optins = UserOptins() 2025-06-01T20:56:27.4317471Z  for user in user_optin_text.split("\n"): 2025-06-01T20:56:27.4318066Z  user = user.strip("\r\n\t -") 2025-06-01T20:56:27.4318653Z  if not user or not user.startswith("@"): 2025-06-01T20:56:27.4319240Z  # Not a valid user. Skip 2025-06-01T20:56:27.4319757Z  continue 2025-06-01T20:56:27.4320230Z  2025-06-01T20:56:27.4320620Z  if user: 2025-06-01T20:56:27.4321116Z  usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:27.4321972Z  optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:27.4322633Z  2025-06-01T20:56:27.4322993Z  return optins 2025-06-01T20:56:27.4323412Z  2025-06-01T20:56:27.4323747Z  2025-06-01T20:56:27.4324260Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:27.4325021Z  """ 2025-06-01T20:56:27.4325451Z  Check if the experiment name is valid. 2025-06-01T20:56:27.4325997Z  A valid name: 2025-06-01T20:56:27.4326688Z  - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:27.4327671Z  - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:27.4328428Z  - Cannot contain spaces 2025-06-01T20:56:27.4328927Z  """ 2025-06-01T20:56:27.4329303Z  2025-06-01T20:56:27.4329780Z  valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:27.4330533Z  valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:27.4331301Z  2025-06-01T20:56:27.4331649Z  if valid: 2025-06-01T20:56:27.4332059Z  return True 2025-06-01T20:56:27.4332482Z  2025-06-01T20:56:27.4332830Z  log.error( 2025-06-01T20:56:27.4334262Z  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-06-01T20:56:27.4336132Z  ) 2025-06-01T20:56:27.4336515Z  return False 2025-06-01T20:56:27.4336932Z  2025-06-01T20:56:27.4337271Z  2025-06-01T20:56:27.4337800Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:27.4338482Z  """ 2025-06-01T20:56:27.4339104Z  Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:27.4339852Z  """ 2025-06-01T20:56:27.4340228Z  try: 2025-06-01T20:56:27.4340624Z  if settings_text: 2025-06-01T20:56:27.4341388Z  # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:27.4342220Z  # for easy reading 2025-06-01T20:56:27.4343064Z  # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:27.4343999Z  # the backtick character in shell commands. 2025-06-01T20:56:27.4345114Z  backtick = chr(96) # backtick character 2025-06-01T20:56:27.4345840Z  settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:27.4346549Z  settings = load_yaml(settings_text) 2025-06-01T20:56:27.4347088Z  2025-06-01T20:56:27.4347693Z  # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:27.4348472Z  experiments = {} 2025-06-01T20:56:27.4348961Z  2025-06-01T20:56:27.4349523Z  for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:27.4350320Z  if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:27.4351421Z  # 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-06-01T20:56:27.4352515Z  continue 2025-06-01T20:56:27.4352994Z  2025-06-01T20:56:27.4353376Z  valid_settings = {} 2025-06-01T20:56:27.4353939Z  for setting in exp_settings: 2025-06-01T20:56:27.4354884Z  if setting not in Experiment._fields: 2025-06-01T20:56:27.4355491Z  log.warning( 2025-06-01T20:56:27.4356239Z  f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:27.4356993Z  ) 2025-06-01T20:56:27.4357464Z  else: 2025-06-01T20:56:27.4358025Z  valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:27.4358620Z  2025-06-01T20:56:27.4359100Z  experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:27.4359774Z  return Settings(experiments) 2025-06-01T20:56:27.4360294Z  2025-06-01T20:56:27.4360668Z  except Exception: 2025-06-01T20:56:27.4361205Z  log.exception("Failed to parse settings") 2025-06-01T20:56:27.4361763Z  2025-06-01T20:56:27.4362127Z  return Settings() 2025-06-01T20:56:27.4362563Z  2025-06-01T20:56:27.4362907Z  2025-06-01T20:56:27.4363492Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:27.4364095Z  """ 2025-06-01T20:56:27.4364564Z  Parse settings, if any, from the rollout state. 2025-06-01T20:56:27.4365286Z  2025-06-01T20:56:27.4365834Z  If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.4366637Z  and the text below is the list of opted in users. 2025-06-01T20:56:27.4367218Z  2025-06-01T20:56:27.4367819Z  If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:27.4368574Z  """ 2025-06-01T20:56:27.4369155Z  settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.4369972Z  return parse_settings_from_text(settings_text) 2025-06-01T20:56:27.4370535Z  2025-06-01T20:56:27.4370872Z  2025-06-01T20:56:27.4371348Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:27.4371940Z  """ 2025-06-01T20:56:27.4372369Z  Parse users from the rollout state. 2025-06-01T20:56:27.4373036Z  2025-06-01T20:56:27.4373388Z  """ 2025-06-01T20:56:27.4373952Z  _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.4374845Z  return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:27.4375412Z  2025-06-01T20:56:27.4375864Z  2025-06-01T20:56:27.4376518Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.4377481Z  """ 2025-06-01T20:56:27.4378170Z  Check if a user is opted into an experiment 2025-06-01T20:56:27.4378885Z  """ 2025-06-01T20:56:27.4379386Z  return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:27.4379997Z  2025-06-01T20:56:27.4380337Z  2025-06-01T20:56:27.4380978Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.4381763Z  """ 2025-06-01T20:56:27.4382264Z  Check if a user explicitly opted out of an experiment 2025-06-01T20:56:27.4382886Z  """ 2025-06-01T20:56:27.4383540Z  # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:27.4384280Z  experiment_optout = "-" + experiment_name 2025-06-01T20:56:27.4385065Z  if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:27.4385694Z  return False 2025-06-01T20:56:27.4386126Z  2025-06-01T20:56:27.4386600Z  if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:27.4387421Z  log.warning( 2025-06-01T20:56:27.4388268Z  f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:27.4389164Z  ) 2025-06-01T20:56:27.4389538Z  2025-06-01T20:56:27.4389893Z  return True 2025-06-01T20:56:27.4390303Z  2025-06-01T20:56:27.4390641Z  2025-06-01T20:56:27.4390998Z def get_runner_prefix( 2025-06-01T20:56:27.4391482Z  rollout_state: str, 2025-06-01T20:56:27.4391997Z  workflow_requestors: Iterable[str], 2025-06-01T20:56:27.4392537Z  branch: str, 2025-06-01T20:56:27.4393086Z  eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.4393796Z  opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.4394413Z  is_canary: bool = False, 2025-06-01T20:56:27.4394993Z ) -> str: 2025-06-01T20:56:27.4395460Z  settings = parse_settings(rollout_state) 2025-06-01T20:56:27.4396065Z  user_optins = parse_users(rollout_state) 2025-06-01T20:56:27.4396608Z  2025-06-01T20:56:27.4397083Z  fleet_prefix = "" 2025-06-01T20:56:27.4397551Z  prefixes = [] 2025-06-01T20:56:27.4398239Z  for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:27.4399215Z  if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:27.4399963Z  log.info( 2025-06-01T20:56:27.4400676Z  f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:27.4401448Z  ) 2025-06-01T20:56:27.4401861Z  continue 2025-06-01T20:56:27.4402288Z  2025-06-01T20:56:27.4402674Z  if opt_out_experiments: 2025-06-01T20:56:27.4403261Z  if experiment_name in opt_out_experiments: 2025-06-01T20:56:27.4403941Z  opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:27.4404552Z  log.info( 2025-06-01T20:56:27.4405595Z  f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:27.4406597Z  ) 2025-06-01T20:56:27.4407030Z  continue 2025-06-01T20:56:27.4407487Z  2025-06-01T20:56:27.4407864Z  if eligible_experiments: 2025-06-01T20:56:27.4408473Z  if experiment_name not in eligible_experiments: 2025-06-01T20:56:27.4409140Z  exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:27.4409726Z  log.info( 2025-06-01T20:56:27.4410541Z  f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:27.4411401Z  ) 2025-06-01T20:56:27.4411837Z  continue 2025-06-01T20:56:27.4412370Z  elif not experiment_settings.default: 2025-06-01T20:56:27.4412933Z  log.info( 2025-06-01T20:56:27.4413672Z  f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:27.4414446Z  ) 2025-06-01T20:56:27.4414945Z  continue 2025-06-01T20:56:27.4415369Z  2025-06-01T20:56:27.4415845Z  # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:27.4416482Z  opted_out_users = [ 2025-06-01T20:56:27.4416970Z  requestor 2025-06-01T20:56:27.4417473Z  for requestor in workflow_requestors 2025-06-01T20:56:27.4418174Z  if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.4418943Z  ] 2025-06-01T20:56:27.4419315Z  2025-06-01T20:56:27.4419690Z  if opted_out_users: 2025-06-01T20:56:27.4420183Z  log.info( 2025-06-01T20:56:27.4420862Z  f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:27.4421564Z  ) 2025-06-01T20:56:27.4421978Z  continue 2025-06-01T20:56:27.4422402Z  2025-06-01T20:56:27.4422876Z  # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:27.4423515Z  opted_in_users = [ 2025-06-01T20:56:27.4424001Z  requestor 2025-06-01T20:56:27.4424726Z  for requestor in workflow_requestors 2025-06-01T20:56:27.4425521Z  if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.4426179Z  ] 2025-06-01T20:56:27.4426546Z  2025-06-01T20:56:27.4426911Z  enabled = False 2025-06-01T20:56:27.4427388Z  if opted_in_users: 2025-06-01T20:56:27.4427998Z  log.info( 2025-06-01T20:56:27.4428663Z  f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:27.4429360Z  ) 2025-06-01T20:56:27.4429786Z  enabled = True 2025-06-01T20:56:27.4430238Z  2025-06-01T20:56:27.4430657Z  elif experiment_settings.rollout_perc: 2025-06-01T20:56:27.4431540Z  # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:27.4432501Z  if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:27.4433178Z  log.info( 2025-06-01T20:56:27.4434065Z  f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:27.4435208Z  ) 2025-06-01T20:56:27.4435667Z  enabled = True 2025-06-01T20:56:27.4436148Z  2025-06-01T20:56:27.4436505Z  if enabled: 2025-06-01T20:56:27.4436975Z  label = experiment_name 2025-06-01T20:56:27.4437568Z  if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:27.4438410Z  # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:27.4439326Z  # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:27.4440119Z  # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:27.4440797Z  if is_canary: 2025-06-01T20:56:27.4441336Z  label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:27.4441912Z  fleet_prefix = label 2025-06-01T20:56:27.4442433Z  else: 2025-06-01T20:56:27.4442909Z  prefixes.append(label) 2025-06-01T20:56:27.4443435Z  2025-06-01T20:56:27.4443807Z  if len(prefixes) > 1: 2025-06-01T20:56:27.4444292Z  log.error( 2025-06-01T20:56:27.4445433Z  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-06-01T20:56:27.4446579Z  ) 2025-06-01T20:56:27.4447001Z  prefixes = prefixes[:1] 2025-06-01T20:56:27.4447493Z  2025-06-01T20:56:27.4447864Z  # Fleet always comes first 2025-06-01T20:56:27.4448373Z  if fleet_prefix: 2025-06-01T20:56:27.4448859Z  prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:27.4449512Z  2025-06-01T20:56:27.4449972Z  return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:27.4450563Z  2025-06-01T20:56:27.4450933Z  2025-06-01T20:56:27.4451607Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:27.4452401Z  """ 2025-06-01T20:56:27.4453013Z  Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:27.4453740Z  2025-06-01T20:56:27.4454313Z  The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:27.4455144Z  """ 2025-06-01T20:56:27.4455559Z  gh = get_gh_client(github_token) 2025-06-01T20:56:27.4456141Z  issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:27.4456825Z  return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:27.4457439Z  2025-06-01T20:56:27.4457785Z  2025-06-01T20:56:27.4458377Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:27.4459283Z  for _ in range(num_retries): 2025-06-01T20:56:27.4459787Z  try: 2025-06-01T20:56:27.4460259Z  req = Request(url=url, headers=headers) 2025-06-01T20:56:27.4460942Z  content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:27.4461627Z  return json.loads(content) 2025-06-01T20:56:27.4462182Z  except Exception as e: 2025-06-01T20:56:27.4462773Z  log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:27.4463347Z  2025-06-01T20:56:27.4463920Z  log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:27.4464770Z  return {} 2025-06-01T20:56:27.4465172Z  2025-06-01T20:56:27.4465508Z  2025-06-01T20:56:27.4465855Z @cache 2025-06-01T20:56:27.4466501Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:27.4467295Z  """ 2025-06-01T20:56:27.4467708Z  Dynamically get PR information 2025-06-01T20:56:27.4468218Z  """ 2025-06-01T20:56:27.4468744Z  github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:27.4469400Z  headers = { 2025-06-01T20:56:27.4469903Z  "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:27.4470537Z  "Authorization": f"token {github_token}", 2025-06-01T20:56:27.4471091Z  } 2025-06-01T20:56:27.4471547Z  json_response: dict[str, Any] = download_json( 2025-06-01T20:56:27.4472188Z  url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:27.4472754Z  headers=headers, 2025-06-01T20:56:27.4473223Z  ) 2025-06-01T20:56:27.4473587Z  2025-06-01T20:56:27.4473951Z  if not json_response: 2025-06-01T20:56:27.4474670Z  log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:27.4475320Z  return {} 2025-06-01T20:56:27.4475755Z  2025-06-01T20:56:27.4476138Z  return json_response 2025-06-01T20:56:27.4476595Z  2025-06-01T20:56:27.4476932Z  2025-06-01T20:56:27.4477522Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:27.4478268Z  """ 2025-06-01T20:56:27.4478826Z  Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:27.4479495Z  """ 2025-06-01T20:56:27.4480003Z  pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:27.4480633Z  return { 2025-06-01T20:56:27.4481237Z  label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:27.4482067Z  } 2025-06-01T20:56:27.4482426Z  2025-06-01T20:56:27.4482761Z  2025-06-01T20:56:27.4483120Z def main() -> None: 2025-06-01T20:56:27.4483592Z  args = parse_args() 2025-06-01T20:56:27.4484040Z  2025-06-01T20:56:27.4484468Z  runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:27.4485126Z  2025-06-01T20:56:27.4485500Z  # Check if the PR is opt-out 2025-06-01T20:56:27.4486027Z  if args.pr_number: 2025-06-01T20:56:27.4486724Z  labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:27.4487513Z  if OPT_OUT_LABEL in labels: 2025-06-01T20:56:27.4488035Z  log.info( 2025-06-01T20:56:27.4488762Z  f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:27.4489558Z  ) 2025-06-01T20:56:27.4490151Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.4490867Z  sys.exit() 2025-06-01T20:56:27.4491416Z  2025-06-01T20:56:27.4491764Z  try: 2025-06-01T20:56:27.4492236Z  rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:27.4492972Z  args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:27.4493636Z  ) 2025-06-01T20:56:27.4494011Z  2025-06-01T20:56:27.4494416Z  username = get_potential_pr_author( 2025-06-01T20:56:27.4495078Z  args.github_token, 2025-06-01T20:56:27.4495603Z  args.github_repo, 2025-06-01T20:56:27.4496116Z  args.github_actor, 2025-06-01T20:56:27.4496646Z  args.github_ref_type, 2025-06-01T20:56:27.4497189Z  args.github_branch, 2025-06-01T20:56:27.4497685Z  ) 2025-06-01T20:56:27.4498066Z  2025-06-01T20:56:27.4498549Z  is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:27.4499173Z  2025-06-01T20:56:27.4499595Z  runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:27.4500169Z  rollout_state, 2025-06-01T20:56:27.4500709Z  (args.github_issue_owner, username), 2025-06-01T20:56:27.4501281Z  args.github_branch, 2025-06-01T20:56:27.4501834Z  args.eligible_experiments, 2025-06-01T20:56:27.4502397Z  args.opt_out_experiments, 2025-06-01T20:56:27.4502929Z  is_canary, 2025-06-01T20:56:27.4503374Z  ) 2025-06-01T20:56:27.4503755Z  2025-06-01T20:56:27.4504125Z  except Exception as e: 2025-06-01T20:56:27.4504715Z  log.error( 2025-06-01T20:56:27.4505440Z  f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:27.4506215Z  ) 2025-06-01T20:56:27.4506591Z  2025-06-01T20:56:27.4507116Z  set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.4507793Z  2025-06-01T20:56:27.4508128Z  2025-06-01T20:56:27.4508498Z if __name__ == "__main__": 2025-06-01T20:56:27.4508964Z  main() 2025-06-01T20:56:27.4509352Z  2025-06-01T20:56:27.4509723Z EOF 2025-06-01T20:56:27.4510075Z  2025-06-01T20:56:27.4510453Z cat runner_determinator.py 2025-06-01T20:56:27.4881846Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:27.4882625Z env: 2025-06-01T20:56:27.4883278Z GITHUB_TOKEN: *** 2025-06-01T20:56:27.4883672Z ISSUE_NUMBER: 5132 2025-06-01T20:56:27.4884106Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:27.4884815Z ISSUE_OWNER: 2025-06-01T20:56:27.4885428Z CHECK_EXPERIMENTS: 2025-06-01T20:56:27.4885848Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:27.4886268Z PR_NUMBER: 2025-06-01T20:56:27.4886637Z ##[endgroup] 2025-06-01T20:56:27.5104049Z # flake8: noqa: G004 2025-06-01T20:56:27.5104539Z 2025-06-01T20:56:27.5105198Z # Note: Copies of this script in runner_determinator.py and _runner-determinator.yml 2025-06-01T20:56:27.5106150Z # must be kept in sync. You can do it easily by running the following command: 2025-06-01T20:56:27.5106917Z # python .github/scripts/update_runner_determinator.py 2025-06-01T20:56:27.5107353Z 2025-06-01T20:56:27.5107497Z """ 2025-06-01T20:56:27.5108057Z This runner determinator is used to determine which set of runners to run a 2025-06-01T20:56:27.5108936Z GitHub job on. It uses the first comment of a GitHub issue (by default 2025-06-01T20:56:27.5109823Z https://github.com/pytorch/test-infra/issues/5132) to define the configuration 2025-06-01T20:56:27.5110612Z of which runners should be used to run which job. 2025-06-01T20:56:27.5111031Z 2025-06-01T20:56:27.5111392Z The configuration has two parts, the settings and a list of opted-in users, 2025-06-01T20:56:27.5112442Z separated by a line containing "---". If the line is not present, the 2025-06-01T20:56:27.5113294Z settings are considered to be empty with only the second part, the user 2025-06-01T20:56:27.5113967Z list, defined. 2025-06-01T20:56:27.5114196Z 2025-06-01T20:56:27.5114534Z The first part is a YAML block that defines the rollout settings. This can be 2025-06-01T20:56:27.5115706Z used to define any settings that are needed to determine which runners to use. 2025-06-01T20:56:27.5116527Z It's fields are defined by the RolloutSettings class below. 2025-06-01T20:56:27.5117028Z 2025-06-01T20:56:27.5117446Z The second part is a list of users who are explicitly opted in to the LF fleet. 2025-06-01T20:56:27.5118302Z The user list is also a comma separated list of additional features or 2025-06-01T20:56:27.5119017Z experiments which the user could be opted in to. 2025-06-01T20:56:27.5119434Z 2025-06-01T20:56:27.5119620Z The user list has the following rules: 2025-06-01T20:56:27.5119976Z 2025-06-01T20:56:27.5120275Z - Users are GitHub usernames, which must start with the @ prefix 2025-06-01T20:56:27.5121119Z - Each user is also a comma-separated list of features/experiments to enable 2025-06-01T20:56:27.5121859Z - A "#" prefix opts the user out of all experiments 2025-06-01T20:56:27.5122260Z 2025-06-01T20:56:27.5122411Z Example config: 2025-06-01T20:56:27.5122856Z # A list of experiments that can be opted into. 2025-06-01T20:56:27.5123519Z # This defines the behavior they'll induce when opted into. 2025-06-01T20:56:27.5124126Z # Expected syntax is: 2025-06-01T20:56:27.5125362Z # [experiment_name]: # Name of the experiment. Also used for the label prefix. 2025-06-01T20:56:27.5126360Z # rollout_perc: [int] # % of workflows to run with this experiment when users are not opted in. 2025-06-01T20:56:27.5126961Z 2025-06-01T20:56:27.5127131Z experiments: 2025-06-01T20:56:27.5127510Z lf: 2025-06-01T20:56:27.5127883Z rollout_percent: 25 2025-06-01T20:56:27.5128331Z all_branches: false 2025-06-01T20:56:27.5128784Z default: true 2025-06-01T20:56:27.5129178Z --- 2025-06-01T20:56:27.5129393Z 2025-06-01T20:56:27.5129546Z # Opt-ins: 2025-06-01T20:56:27.5130116Z # Users can opt into the LF fleet by adding their GitHub username to this list 2025-06-01T20:56:27.5130963Z # and specifying experiments to enable in a comma-separated list. 2025-06-01T20:56:27.5131724Z # To always opt out of an experiment, prefix it with a "-". 2025-06-01T20:56:27.5132363Z # Experiments should be from the above list. 2025-06-01T20:56:27.5132747Z 2025-06-01T20:56:27.5132924Z @User1,-lf,split_build 2025-06-01T20:56:27.5133357Z @User2,lf 2025-06-01T20:56:27.5133738Z @User3,split_build 2025-06-01T20:56:27.5134134Z """ 2025-06-01T20:56:27.5134337Z 2025-06-01T20:56:27.5134487Z import json 2025-06-01T20:56:27.5135281Z import logging 2025-06-01T20:56:27.5135702Z import os 2025-06-01T20:56:27.5136087Z import random 2025-06-01T20:56:27.5136462Z import re 2025-06-01T20:56:27.5136826Z import sys 2025-06-01T20:56:27.5137231Z from argparse import ArgumentParser 2025-06-01T20:56:27.5137766Z from collections.abc import Iterable 2025-06-01T20:56:27.5138279Z from functools import cache 2025-06-01T20:56:27.5138751Z from logging import LogRecord 2025-06-01T20:56:27.5139227Z from typing import Any, NamedTuple 2025-06-01T20:56:27.5139758Z from urllib.request import Request, urlopen 2025-06-01T20:56:27.5140128Z 2025-06-01T20:56:27.5140280Z import yaml 2025-06-01T20:56:27.5140669Z from github import Auth, Github 2025-06-01T20:56:27.5141142Z from github.Issue import Issue 2025-06-01T20:56:27.5141456Z 2025-06-01T20:56:27.5141462Z 2025-06-01T20:56:27.5141669Z DEFAULT_LABEL_PREFIX = "" # use meta runners 2025-06-01T20:56:27.5142357Z WORKFLOW_LABEL_LF = "lf." # use runners from the linux foundation 2025-06-01T20:56:27.5143215Z WORKFLOW_LABEL_LF_CANARY = "lf.c." # use canary runners from the linux foundation 2025-06-01T20:56:27.5143783Z 2025-06-01T20:56:27.5144033Z GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT", "") 2025-06-01T20:56:27.5144958Z GH_OUTPUT_KEY_AMI = "runner-ami" 2025-06-01T20:56:27.5145513Z GH_OUTPUT_KEY_LABEL_TYPE = "label-type" 2025-06-01T20:56:27.5146064Z OPT_OUT_LABEL = "no-runner-experiments" 2025-06-01T20:56:27.5146436Z 2025-06-01T20:56:27.5146616Z SETTING_EXPERIMENTS = "experiments" 2025-06-01T20:56:27.5146953Z 2025-06-01T20:56:27.5147135Z LF_FLEET_EXPERIMENT = "lf" 2025-06-01T20:56:27.5147585Z CANARY_FLEET_SUFFIX = ".c" 2025-06-01T20:56:27.5147883Z 2025-06-01T20:56:27.5147889Z 2025-06-01T20:56:27.5148071Z class Experiment(NamedTuple): 2025-06-01T20:56:27.5148542Z rollout_perc: float = ( 2025-06-01T20:56:27.5149172Z 0 # Percentage of workflows to experiment on when user is not opted-in. 2025-06-01T20:56:27.5149839Z ) 2025-06-01T20:56:27.5150198Z all_branches: bool = ( 2025-06-01T20:56:27.5150821Z False # If True, the experiment is also enabled on the exception branches 2025-06-01T20:56:27.5151479Z ) 2025-06-01T20:56:27.5151832Z default: bool = ( 2025-06-01T20:56:27.5152392Z True # If True, the experiment is enabled by default for all queries 2025-06-01T20:56:27.5153032Z ) 2025-06-01T20:56:27.5153238Z 2025-06-01T20:56:27.5153408Z # Add more fields as needed 2025-06-01T20:56:27.5153723Z 2025-06-01T20:56:27.5153729Z 2025-06-01T20:56:27.5153897Z class Settings(NamedTuple): 2025-06-01T20:56:27.5154336Z """ 2025-06-01T20:56:27.5154981Z Settings for the experiments that can be opted into. 2025-06-01T20:56:27.5155555Z """ 2025-06-01T20:56:27.5155757Z 2025-06-01T20:56:27.5155958Z experiments: dict[str, Experiment] = {} 2025-06-01T20:56:27.5156337Z 2025-06-01T20:56:27.5156344Z 2025-06-01T20:56:27.5156545Z class ColorFormatter(logging.Formatter): 2025-06-01T20:56:27.5157157Z """Color codes the log messages based on the log level""" 2025-06-01T20:56:27.5157605Z 2025-06-01T20:56:27.5157753Z COLORS = { 2025-06-01T20:56:27.5158158Z "WARNING": "\033[33m", # Yellow 2025-06-01T20:56:27.5158662Z "ERROR": "\033[31m", # Red 2025-06-01T20:56:27.5159157Z "CRITICAL": "\033[31m", # Red 2025-06-01T20:56:27.5159647Z "INFO": "\033[0m", # Reset 2025-06-01T20:56:27.5160130Z "DEBUG": "\033[0m", # Reset 2025-06-01T20:56:27.5160589Z } 2025-06-01T20:56:27.5160795Z 2025-06-01T20:56:27.5160996Z def format(self, record: LogRecord) -> str: 2025-06-01T20:56:27.5161743Z log_color = self.COLORS.get(record.levelname, "\033[0m") # Default to reset 2025-06-01T20:56:27.5162505Z record.msg = f"{log_color}{record.msg}\033[0m" 2025-06-01T20:56:27.5163080Z return super().format(record) 2025-06-01T20:56:27.5163424Z 2025-06-01T20:56:27.5163430Z 2025-06-01T20:56:27.5163613Z handler = logging.StreamHandler() 2025-06-01T20:56:27.5164294Z handler.setFormatter(ColorFormatter(fmt="%(levelname)-8s: %(message)s")) 2025-06-01T20:56:27.5165142Z 2025-06-01T20:56:27.5165371Z log = logging.getLogger(os.path.basename(__file__)) 2025-06-01T20:56:27.5165943Z log.addHandler(handler) 2025-06-01T20:56:27.5166371Z log.setLevel(logging.INFO) 2025-06-01T20:56:27.5166674Z 2025-06-01T20:56:27.5166680Z 2025-06-01T20:56:27.5166912Z def set_github_output(key: str, value: str) -> None: 2025-06-01T20:56:27.5167473Z """ 2025-06-01T20:56:27.5167960Z Defines outputs of the github action that invokes this script 2025-06-01T20:56:27.5168569Z """ 2025-06-01T20:56:27.5168928Z if not GITHUB_OUTPUT: 2025-06-01T20:56:27.5169967Z # See https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ for deprecation notice 2025-06-01T20:56:27.5171028Z log.warning( 2025-06-01T20:56:27.5171843Z "No env var found for GITHUB_OUTPUT, you must be running this code locally. Falling back to the deprecated print method." 2025-06-01T20:56:27.5172730Z ) 2025-06-01T20:56:27.5182833Z print(f"::set-output name={key}::{value}") 2025-06-01T20:56:27.5183430Z return 2025-06-01T20:56:27.5183676Z 2025-06-01T20:56:27.5184052Z with open(GITHUB_OUTPUT, "a") as f: 2025-06-01T20:56:27.5184920Z log.info(f"Setting output: {key}='{value}'") 2025-06-01T20:56:27.5185519Z f.write(f"{key}={value}\n") 2025-06-01T20:56:27.5185858Z 2025-06-01T20:56:27.5185866Z 2025-06-01T20:56:27.5186156Z def _str_comma_separated_to_set(value: str) -> frozenset[str]: 2025-06-01T20:56:27.5186779Z return frozenset( 2025-06-01T20:56:27.5187370Z filter(lambda itm: itm != "", map(str.strip, value.strip(" \n\t").split(","))) 2025-06-01T20:56:27.5188046Z ) 2025-06-01T20:56:27.5188246Z 2025-06-01T20:56:27.5188252Z 2025-06-01T20:56:27.5188425Z def parse_args() -> Any: 2025-06-01T20:56:27.5188977Z parser = ArgumentParser("Get dynamic rollout settings") 2025-06-01T20:56:27.5189813Z parser.add_argument("--github-token", type=str, required=True, help="GitHub token") 2025-06-01T20:56:27.5190571Z parser.add_argument( 2025-06-01T20:56:27.5191019Z "--github-issue-repo", 2025-06-01T20:56:27.5191465Z type=str, 2025-06-01T20:56:27.5191856Z required=False, 2025-06-01T20:56:27.5192298Z default="pytorch/test-infra", 2025-06-01T20:56:27.5192824Z help="GitHub repo to get the issue", 2025-06-01T20:56:27.5193323Z ) 2025-06-01T20:56:27.5193689Z parser.add_argument( 2025-06-01T20:56:27.5194118Z "--github-repo", 2025-06-01T20:56:27.5194563Z type=str, 2025-06-01T20:56:27.5195144Z required=True, 2025-06-01T20:56:27.5195619Z help="GitHub repo where CI is running", 2025-06-01T20:56:27.5196137Z ) 2025-06-01T20:56:27.5196502Z parser.add_argument( 2025-06-01T20:56:27.5197096Z "--github-issue", type=int, required=True, help="GitHub issue number" 2025-06-01T20:56:27.5197732Z ) 2025-06-01T20:56:27.5198086Z parser.add_argument( 2025-06-01T20:56:27.5198685Z "--github-actor", type=str, required=True, help="GitHub triggering_actor" 2025-06-01T20:56:27.5199350Z ) 2025-06-01T20:56:27.5199704Z parser.add_argument( 2025-06-01T20:56:27.5200326Z "--github-issue-owner", type=str, required=True, help="GitHub issue owner" 2025-06-01T20:56:27.5200995Z ) 2025-06-01T20:56:27.5201349Z parser.add_argument( 2025-06-01T20:56:27.5201989Z "--github-branch", type=str, required=True, help="Current GitHub branch or tag" 2025-06-01T20:56:27.5202682Z ) 2025-06-01T20:56:27.5203035Z parser.add_argument( 2025-06-01T20:56:27.5203469Z "--github-ref-type", 2025-06-01T20:56:27.5203916Z type=str, 2025-06-01T20:56:27.5204301Z required=True, 2025-06-01T20:56:27.5204925Z help="Current GitHub ref type, branch or tag", 2025-06-01T20:56:27.5205480Z ) 2025-06-01T20:56:27.5205839Z parser.add_argument( 2025-06-01T20:56:27.5206292Z "--eligible-experiments", 2025-06-01T20:56:27.5206794Z type=_str_comma_separated_to_set, 2025-06-01T20:56:27.5207454Z required=False, 2025-06-01T20:56:27.5207863Z default="", 2025-06-01T20:56:27.5208674Z help="comma separated list of experiments to check, if omitted all experiments marked with default=True are checked", 2025-06-01T20:56:27.5209570Z ) 2025-06-01T20:56:27.5209926Z parser.add_argument( 2025-06-01T20:56:27.5210375Z "--opt-out-experiments", 2025-06-01T20:56:27.5210871Z type=_str_comma_separated_to_set, 2025-06-01T20:56:27.5211371Z required=False, 2025-06-01T20:56:27.5259403Z default="", 2025-06-01T20:56:27.5259965Z help=( 2025-06-01T20:56:27.5260704Z "comma separated list of experiments to opt-out of. If unset, no opt-outs will occur. " 2025-06-01T20:56:27.5261822Z "If the same experiment is listed both here and in '--eligible-experiments' opt-out will take priority." 2025-06-01T20:56:27.5262642Z ), 2025-06-01T20:56:27.5263000Z ) 2025-06-01T20:56:27.5263366Z parser.add_argument( 2025-06-01T20:56:27.5263817Z "--pr-number", 2025-06-01T20:56:27.5264238Z type=str, 2025-06-01T20:56:27.5264853Z required=False, 2025-06-01T20:56:27.5265288Z default="", 2025-06-01T20:56:27.5265933Z help="the optional PR number where this is run", 2025-06-01T20:56:27.5266513Z ) 2025-06-01T20:56:27.5266722Z 2025-06-01T20:56:27.5266905Z return parser.parse_args() 2025-06-01T20:56:27.5267229Z 2025-06-01T20:56:27.5267236Z 2025-06-01T20:56:27.5267618Z def get_gh_client(github_token: str) -> Github: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.5268361Z auth = Auth.Token(github_token) 2025-06-01T20:56:27.5268862Z return Github(auth=auth) 2025-06-01T20:56:27.5269163Z 2025-06-01T20:56:27.5269175Z 2025-06-01T20:56:27.5269608Z def get_issue(gh: Github, repo: str, issue_num: int) -> Issue: # type: ignore[no-any-unimported] 2025-06-01T20:56:27.5270378Z repo = gh.get_repo(repo) 2025-06-01T20:56:27.5270878Z return repo.get_issue(number=issue_num) 2025-06-01T20:56:27.5271261Z 2025-06-01T20:56:27.5271268Z 2025-06-01T20:56:27.5271451Z def get_potential_pr_author( 2025-06-01T20:56:27.5272087Z github_token: str, repo: str, username: str, ref_type: str, ref_name: str 2025-06-01T20:56:27.5272753Z ) -> str: 2025-06-01T20:56:27.5273256Z # If the trigger was a new tag added by a bot, this is a ciflow case 2025-06-01T20:56:27.5274045Z # Fetch the actual username from the original PR. The PR number is 2025-06-01T20:56:27.5275001Z # embedded in the tag name: ciflow// 2025-06-01T20:56:27.5275441Z 2025-06-01T20:56:27.5275623Z gh = get_gh_client(github_token) 2025-06-01T20:56:27.5275966Z 2025-06-01T20:56:27.5276227Z if username == "pytorch-bot[bot]" and ref_type == "tag": 2025-06-01T20:56:27.5276844Z split_tag = ref_name.split("/") 2025-06-01T20:56:27.5277340Z if ( 2025-06-01T20:56:27.5277724Z len(split_tag) == 3 2025-06-01T20:56:27.5278208Z and split_tag[0] == "ciflow" 2025-06-01T20:56:27.5278745Z and split_tag[2].isnumeric() 2025-06-01T20:56:27.5279249Z ): 2025-06-01T20:56:27.5279621Z pr_number = split_tag[2] 2025-06-01T20:56:27.5280104Z try: 2025-06-01T20:56:27.5280534Z repository = gh.get_repo(repo) 2025-06-01T20:56:27.5281148Z pull = repository.get_pull(number=int(pr_number)) 2025-06-01T20:56:27.5281742Z except Exception as e: 2025-06-01T20:56:27.5282260Z raise Exception( # noqa: TRY002 2025-06-01T20:56:27.5282919Z f"issue with pull request {pr_number} from repo {repository}" 2025-06-01T20:56:27.5283553Z ) from e 2025-06-01T20:56:27.5284089Z return pull.user.login # type: ignore[no-any-return] 2025-06-01T20:56:27.5284991Z # In all other cases, return the original input username 2025-06-01T20:56:27.5285603Z return username 2025-06-01T20:56:27.5285847Z 2025-06-01T20:56:27.5285853Z 2025-06-01T20:56:27.5286077Z def is_exception_branch(branch: str) -> bool: 2025-06-01T20:56:27.5286790Z """ 2025-06-01T20:56:27.5287420Z Branches that get opted out of experiments by default, until they're explicitly enabled. 2025-06-01T20:56:27.5288177Z """ 2025-06-01T20:56:27.5288723Z return branch.split("/")[0] in {"main", "nightly", "release", "landchecks"} 2025-06-01T20:56:27.5289240Z 2025-06-01T20:56:27.5289246Z 2025-06-01T20:56:27.5289434Z def load_yaml(yaml_text: str) -> Any: 2025-06-01T20:56:27.5289927Z try: 2025-06-01T20:56:27.5290318Z data = yaml.safe_load(yaml_text) 2025-06-01T20:56:27.5290813Z return data 2025-06-01T20:56:27.5291231Z except yaml.YAMLError: 2025-06-01T20:56:27.5291714Z log.exception("Error loading YAML") 2025-06-01T20:56:27.5292224Z raise 2025-06-01T20:56:27.5292450Z 2025-06-01T20:56:27.5292455Z 2025-06-01T20:56:27.5292841Z def extract_settings_user_opt_in_from_text(rollout_state: str) -> tuple[str, str]: 2025-06-01T20:56:27.5293565Z """ 2025-06-01T20:56:27.5294168Z Extracts the text with settings, if any, and the opted in users from the rollout state. 2025-06-01T20:56:27.5294915Z 2025-06-01T20:56:27.5295359Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.5296115Z and the text below is the list of opted in users. 2025-06-01T20:56:27.5296539Z 2025-06-01T20:56:27.5296892Z If it doesn't contain "---" then the settings are empty and the rest is the users. 2025-06-01T20:56:27.5297584Z """ 2025-06-01T20:56:27.5298013Z rollout_state_parts = rollout_state.split("---") 2025-06-01T20:56:27.5298608Z if len(rollout_state_parts) >= 2: 2025-06-01T20:56:27.5299197Z return rollout_state_parts[0], rollout_state_parts[1] 2025-06-01T20:56:27.5299780Z else: 2025-06-01T20:56:27.5300185Z return "", rollout_state 2025-06-01T20:56:27.5300495Z 2025-06-01T20:56:27.5300501Z 2025-06-01T20:56:27.5300685Z class UserOptins(dict[str, list[str]]): 2025-06-01T20:56:27.5301198Z """ 2025-06-01T20:56:27.5301710Z Dictionary of users with a list of features they have opted into 2025-06-01T20:56:27.5302350Z """ 2025-06-01T20:56:27.5302548Z 2025-06-01T20:56:27.5302555Z 2025-06-01T20:56:27.5302876Z def parse_user_opt_in_from_text(user_optin_text: str) -> UserOptins: 2025-06-01T20:56:27.5303527Z """ 2025-06-01T20:56:27.5304214Z Parse the user opt-in text into a key value pair of username and the list of features they have opted into 2025-06-01T20:56:27.5304995Z 2025-06-01T20:56:27.5305576Z Users are GitHub usernames with the @ prefix. Each user is also a comma-separated list of features/experiments to enable. 2025-06-01T20:56:27.5306542Z - Example line: "@User1,lf,split_build" 2025-06-01T20:56:27.5307215Z - A "#" prefix indicates the user is opted out of all experiments 2025-06-01T20:56:27.5307704Z 2025-06-01T20:56:27.5307710Z 2025-06-01T20:56:27.5307856Z """ 2025-06-01T20:56:27.5308229Z optins = UserOptins() 2025-06-01T20:56:27.5308702Z for user in user_optin_text.split("\n"): 2025-06-01T20:56:27.5309273Z user = user.strip("\r\n\t -") 2025-06-01T20:56:27.5309814Z if not user or not user.startswith("@"): 2025-06-01T20:56:27.5310383Z # Not a valid user. Skip 2025-06-01T20:56:27.5310865Z continue 2025-06-01T20:56:27.5311119Z 2025-06-01T20:56:27.5311266Z if user: 2025-06-01T20:56:27.5311687Z usr_name = user.split(",")[0].strip("@") 2025-06-01T20:56:27.5312379Z optins[usr_name] = [exp.strip(" ") for exp in user.split(",")[1:]] 2025-06-01T20:56:27.5312873Z 2025-06-01T20:56:27.5313030Z return optins 2025-06-01T20:56:27.5313280Z 2025-06-01T20:56:27.5313286Z 2025-06-01T20:56:27.5313555Z def is_valid_experiment_name(experiment_name: str) -> bool: 2025-06-01T20:56:27.5314153Z """ 2025-06-01T20:56:27.5314536Z Check if the experiment name is valid. 2025-06-01T20:56:27.5315326Z A valid name: 2025-06-01T20:56:27.5315953Z - Contains only alphanumeric characters and the special characters "_" & "-" 2025-06-01T20:56:27.5317014Z - The special characters "_" & "-" shouldn't be the first or last characters 2025-06-01T20:56:27.5317732Z - Cannot contain spaces 2025-06-01T20:56:27.5318189Z """ 2025-06-01T20:56:27.5318396Z 2025-06-01T20:56:27.5318650Z valid_char_regex = r"^[a-zA-Z0-9]([\w-]*[a-zA-Z0-9])?$" 2025-06-01T20:56:27.5319342Z valid = bool(re.match(valid_char_regex, experiment_name)) 2025-06-01T20:56:27.5319797Z 2025-06-01T20:56:27.5319946Z if valid: 2025-06-01T20:56:27.5320323Z return True 2025-06-01T20:56:27.5320571Z 2025-06-01T20:56:27.5320721Z log.error( 2025-06-01T20:56:27.5322115Z 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-06-01T20:56:27.5323638Z ) 2025-06-01T20:56:27.5323989Z return False 2025-06-01T20:56:27.5324235Z 2025-06-01T20:56:27.5324242Z 2025-06-01T20:56:27.5324531Z def parse_settings_from_text(settings_text: str) -> Settings: 2025-06-01T20:56:27.5325291Z """ 2025-06-01T20:56:27.5325984Z Parse the experiments from the issue body into a list of ExperimentSettings 2025-06-01T20:56:27.5326696Z """ 2025-06-01T20:56:27.5327045Z try: 2025-06-01T20:56:27.5327398Z if settings_text: 2025-06-01T20:56:27.5328105Z # Escape the backtick as well so that we can have the settings in a code block on the GH issue 2025-06-01T20:56:27.5328873Z # for easy reading 2025-06-01T20:56:27.5329650Z # Note: Using ascii for the backtick so that the cat step in _runner-determinator.yml doesn't choke on 2025-06-01T20:56:27.5330510Z # the backtick character in shell commands. 2025-06-01T20:56:27.5331104Z backtick = chr(96) # backtick character 2025-06-01T20:56:27.5331759Z settings_text = settings_text.strip(f"\r\n\t{backtick} ") 2025-06-01T20:56:27.5332418Z settings = load_yaml(settings_text) 2025-06-01T20:56:27.5332798Z 2025-06-01T20:56:27.5333175Z # For now we just load experiments. We can expand this if/when we add more settings 2025-06-01T20:56:27.5333906Z experiments = {} 2025-06-01T20:56:27.5334213Z 2025-06-01T20:56:27.5334545Z for exp_name, exp_settings in settings.get(SETTING_EXPERIMENTS).items(): 2025-06-01T20:56:27.5335392Z if not is_valid_experiment_name(exp_name): 2025-06-01T20:56:27.5336470Z # 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-06-01T20:56:27.5337478Z continue 2025-06-01T20:56:27.5337770Z 2025-06-01T20:56:27.5337940Z valid_settings = {} 2025-06-01T20:56:27.5338460Z for setting in exp_settings: 2025-06-01T20:56:27.5339025Z if setting not in Experiment._fields: 2025-06-01T20:56:27.5339584Z log.warning( 2025-06-01T20:56:27.5340285Z f"Unexpected setting in experiment: {setting} = {exp_settings[setting]}" 2025-06-01T20:56:27.5341003Z ) 2025-06-01T20:56:27.5341439Z else: 2025-06-01T20:56:27.5341948Z valid_settings[setting] = exp_settings[setting] 2025-06-01T20:56:27.5342373Z 2025-06-01T20:56:27.5342626Z experiments[exp_name] = Experiment(**valid_settings) 2025-06-01T20:56:27.5343253Z return Settings(experiments) 2025-06-01T20:56:27.5343616Z 2025-06-01T20:56:27.5343773Z except Exception: 2025-06-01T20:56:27.5344279Z log.exception("Failed to parse settings") 2025-06-01T20:56:27.5344766Z 2025-06-01T20:56:27.5344923Z return Settings() 2025-06-01T20:56:27.5345194Z 2025-06-01T20:56:27.5345200Z 2025-06-01T20:56:27.5345439Z def parse_settings(rollout_state: str) -> Settings: 2025-06-01T20:56:27.5346003Z """ 2025-06-01T20:56:27.5346564Z Parse settings, if any, from the rollout state. 2025-06-01T20:56:27.5346973Z 2025-06-01T20:56:27.5347308Z If the issue body contains "---" then the text above that is the settings 2025-06-01T20:56:27.5348046Z and the text below is the list of opted in users. 2025-06-01T20:56:27.5348457Z 2025-06-01T20:56:27.5348847Z If it doesn't contain "---" then the settings are empty and the default values are used. 2025-06-01T20:56:27.5349554Z """ 2025-06-01T20:56:27.5350090Z settings_text, _ = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.5350829Z return parse_settings_from_text(settings_text) 2025-06-01T20:56:27.5351243Z 2025-06-01T20:56:27.5351250Z 2025-06-01T20:56:27.5351479Z def parse_users(rollout_state: str) -> UserOptins: 2025-06-01T20:56:27.5352036Z """ 2025-06-01T20:56:27.5352421Z Parse users from the rollout state. 2025-06-01T20:56:27.5352777Z 2025-06-01T20:56:27.5352929Z """ 2025-06-01T20:56:27.5353435Z _, users_text = extract_settings_user_opt_in_from_text(rollout_state) 2025-06-01T20:56:27.5354164Z return parse_user_opt_in_from_text(users_text) 2025-06-01T20:56:27.5354738Z 2025-06-01T20:56:27.5354748Z 2025-06-01T20:56:27.5355384Z def is_user_opted_in(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.5356125Z """ 2025-06-01T20:56:27.5356535Z Check if a user is opted into an experiment 2025-06-01T20:56:27.5357058Z """ 2025-06-01T20:56:27.5357491Z return experiment_name in user_optins.get(user, []) 2025-06-01T20:56:27.5357905Z 2025-06-01T20:56:27.5357910Z 2025-06-01T20:56:27.5358300Z def is_user_opted_out(user: str, user_optins: UserOptins, experiment_name: str) -> bool: 2025-06-01T20:56:27.5359027Z """ 2025-06-01T20:56:27.5359462Z Check if a user explicitly opted out of an experiment 2025-06-01T20:56:27.5360041Z """ 2025-06-01T20:56:27.5360530Z # if the experiment is prefixed with a "-", then it's an opt-out 2025-06-01T20:56:27.5361193Z experiment_optout = "-" + experiment_name 2025-06-01T20:56:27.5361826Z if experiment_optout not in user_optins.get(user, []): 2025-06-01T20:56:27.5362401Z return False 2025-06-01T20:56:27.5362669Z 2025-06-01T20:56:27.5362926Z if is_user_opted_in(user, user_optins, experiment_name): 2025-06-01T20:56:27.5363517Z log.warning( 2025-06-01T20:56:27.5364298Z f"User {user} is opted into experiment {experiment_name}, but also opted out of it. Defaulting to opting out" 2025-06-01T20:56:27.5365271Z ) 2025-06-01T20:56:27.5365486Z 2025-06-01T20:56:27.5365635Z return True 2025-06-01T20:56:27.5365878Z 2025-06-01T20:56:27.5365884Z 2025-06-01T20:56:27.5366052Z def get_runner_prefix( 2025-06-01T20:56:27.5366468Z rollout_state: str, 2025-06-01T20:56:27.5366923Z workflow_requestors: Iterable[str], 2025-06-01T20:56:27.5367414Z branch: str, 2025-06-01T20:56:27.5367913Z eligible_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.5368556Z opt_out_experiments: frozenset[str] = frozenset(), 2025-06-01T20:56:27.5369139Z is_canary: bool = False, 2025-06-01T20:56:27.5369592Z ) -> str: 2025-06-01T20:56:27.5369988Z settings = parse_settings(rollout_state) 2025-06-01T20:56:27.5370559Z user_optins = parse_users(rollout_state) 2025-06-01T20:56:27.5370945Z 2025-06-01T20:56:27.5371103Z fleet_prefix = "" 2025-06-01T20:56:27.5371517Z prefixes = [] 2025-06-01T20:56:27.5372134Z for experiment_name, experiment_settings in settings.experiments.items(): 2025-06-01T20:56:27.5373032Z if not experiment_settings.all_branches and is_exception_branch(branch): 2025-06-01T20:56:27.5373727Z log.info( 2025-06-01T20:56:27.5374375Z f"Branch {branch} is an exception branch. Not enabling experiment {experiment_name}." 2025-06-01T20:56:27.5375209Z ) 2025-06-01T20:56:27.5375581Z continue 2025-06-01T20:56:27.5375831Z 2025-06-01T20:56:27.5376000Z if opt_out_experiments: 2025-06-01T20:56:27.5376517Z if experiment_name in opt_out_experiments: 2025-06-01T20:56:27.5377287Z opt_out_exp_list = ", ".join(opt_out_experiments) 2025-06-01T20:56:27.5377857Z log.info( 2025-06-01T20:56:27.5378743Z f"Skipping experiment '{experiment_name}', as this workflow has opted-out (opted out experiments are: {opt_out_exp_list})" 2025-06-01T20:56:27.5379679Z ) 2025-06-01T20:56:27.5380066Z continue 2025-06-01T20:56:27.5380335Z 2025-06-01T20:56:27.5380514Z if eligible_experiments: 2025-06-01T20:56:27.5381070Z if experiment_name not in eligible_experiments: 2025-06-01T20:56:27.5381700Z exp_list = ", ".join(eligible_experiments) 2025-06-01T20:56:27.5382239Z log.info( 2025-06-01T20:56:27.5382988Z f"Skipping experiment '{experiment_name}', as it is not in the eligible_experiments list: {exp_list}" 2025-06-01T20:56:27.5383795Z ) 2025-06-01T20:56:27.5384173Z continue 2025-06-01T20:56:27.5384741Z elif not experiment_settings.default: 2025-06-01T20:56:27.5385266Z log.info( 2025-06-01T20:56:27.5386006Z f"Skipping experiment '{experiment_name}', as it is not a default experiment" 2025-06-01T20:56:27.5386732Z ) 2025-06-01T20:56:27.5387103Z continue 2025-06-01T20:56:27.5387352Z 2025-06-01T20:56:27.5387605Z # Is any workflow_requestor opted out to this experiment? 2025-06-01T20:56:27.5388212Z opted_out_users = [ 2025-06-01T20:56:27.5388651Z requestor 2025-06-01T20:56:27.5389083Z for requestor in workflow_requestors 2025-06-01T20:56:27.5389744Z if is_user_opted_out(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.5390356Z ] 2025-06-01T20:56:27.5390570Z 2025-06-01T20:56:27.5390731Z if opted_out_users: 2025-06-01T20:56:27.5391158Z log.info( 2025-06-01T20:56:27.5391745Z f"{', '.join(opted_out_users)} have opted out of experiment {experiment_name}." 2025-06-01T20:56:27.5392428Z ) 2025-06-01T20:56:27.5392799Z continue 2025-06-01T20:56:27.5393044Z 2025-06-01T20:56:27.5393300Z # Is any workflow_requestor opted in to this experiment? 2025-06-01T20:56:27.5393897Z opted_in_users = [ 2025-06-01T20:56:27.5394335Z requestor 2025-06-01T20:56:27.5395035Z for requestor in workflow_requestors 2025-06-01T20:56:27.5395685Z if is_user_opted_in(requestor, user_optins, experiment_name) 2025-06-01T20:56:27.5396278Z ] 2025-06-01T20:56:27.5396490Z 2025-06-01T20:56:27.5396647Z enabled = False 2025-06-01T20:56:27.5397069Z if opted_in_users: 2025-06-01T20:56:27.5397492Z log.info( 2025-06-01T20:56:27.5398061Z f"{', '.join(opted_in_users)} have opted into experiment {experiment_name}." 2025-06-01T20:56:27.5398720Z ) 2025-06-01T20:56:27.5399109Z enabled = True 2025-06-01T20:56:27.5399406Z 2025-06-01T20:56:27.5399608Z elif experiment_settings.rollout_perc: 2025-06-01T20:56:27.5400410Z # If no user is opted in, then we randomly enable the experiment based on the rollout percentage 2025-06-01T20:56:27.5401321Z if random.uniform(0, 100) <= experiment_settings.rollout_perc: 2025-06-01T20:56:27.5401957Z log.info( 2025-06-01T20:56:27.5402790Z f"Based on rollout percentage of {experiment_settings.rollout_perc}%, enabling experiment {experiment_name}." 2025-06-01T20:56:27.5403677Z ) 2025-06-01T20:56:27.5404077Z enabled = True 2025-06-01T20:56:27.5404377Z 2025-06-01T20:56:27.5404526Z if enabled: 2025-06-01T20:56:27.5405375Z label = experiment_name 2025-06-01T20:56:27.5405910Z if experiment_name == LF_FLEET_EXPERIMENT: 2025-06-01T20:56:27.5406709Z # We give some special treatment to the "lf" experiment since determines the fleet we use 2025-06-01T20:56:27.5407698Z # - If it's enabled, then we always list it's prefix first 2025-06-01T20:56:27.5408442Z # - If we're in the canary branch, then we append ".c" to the lf prefix 2025-06-01T20:56:27.5409085Z if is_canary: 2025-06-01T20:56:27.5409569Z label += CANARY_FLEET_SUFFIX 2025-06-01T20:56:27.5410107Z fleet_prefix = label 2025-06-01T20:56:27.5410586Z else: 2025-06-01T20:56:27.5411005Z prefixes.append(label) 2025-06-01T20:56:27.5411356Z 2025-06-01T20:56:27.5411529Z if len(prefixes) > 1: 2025-06-01T20:56:27.5411958Z log.error( 2025-06-01T20:56:27.5412942Z 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-06-01T20:56:27.5413999Z ) 2025-06-01T20:56:27.5414374Z prefixes = prefixes[:1] 2025-06-01T20:56:27.5414785Z 2025-06-01T20:56:27.5414966Z # Fleet always comes first 2025-06-01T20:56:27.5415432Z if fleet_prefix: 2025-06-01T20:56:27.5415874Z prefixes.insert(0, fleet_prefix) 2025-06-01T20:56:27.5416245Z 2025-06-01T20:56:27.5416585Z return ".".join(prefixes) + "." if prefixes else "" 2025-06-01T20:56:27.5417011Z 2025-06-01T20:56:27.5417017Z 2025-06-01T20:56:27.5417428Z def get_rollout_state_from_issue(github_token: str, repo: str, issue_num: int) -> str: 2025-06-01T20:56:27.5418170Z """ 2025-06-01T20:56:27.5418725Z Gets the first comment of the issue, which contains the desired rollout state. 2025-06-01T20:56:27.5419284Z 2025-06-01T20:56:27.5419640Z The default issue we use - https://github.com/pytorch/test-infra/issues/5132 2025-06-01T20:56:27.5420322Z """ 2025-06-01T20:56:27.5420702Z gh = get_gh_client(github_token) 2025-06-01T20:56:27.5421224Z issue = get_issue(gh, repo, issue_num) 2025-06-01T20:56:27.5421840Z return str(issue.get_comments()[0].body.strip("\n\t ")) 2025-06-01T20:56:27.5422287Z 2025-06-01T20:56:27.5422293Z 2025-06-01T20:56:27.5422663Z def download_json(url: str, headers: dict[str, str], num_retries: int = 3) -> Any: 2025-06-01T20:56:27.5423401Z for _ in range(num_retries): 2025-06-01T20:56:27.5423869Z try: 2025-06-01T20:56:27.5424279Z req = Request(url=url, headers=headers) 2025-06-01T20:56:27.5425037Z content = urlopen(req, timeout=5).read().decode("utf-8") 2025-06-01T20:56:27.5425658Z return json.loads(content) 2025-06-01T20:56:27.5426174Z except Exception as e: 2025-06-01T20:56:27.5426695Z log.warning(f"Could not download {url}: {e}") 2025-06-01T20:56:27.5427105Z 2025-06-01T20:56:27.5427460Z log.warning(f"All {num_retries} retries exhausted, downloading {url} failed") 2025-06-01T20:56:27.5428150Z return {} 2025-06-01T20:56:27.5428385Z 2025-06-01T20:56:27.5428391Z 2025-06-01T20:56:27.5428535Z @cache 2025-06-01T20:56:27.5429127Z def get_pr_info(github_repo: str, github_token: str, pr_number: int) -> dict[str, Any]: 2025-06-01T20:56:27.5429853Z """ 2025-06-01T20:56:27.5430241Z Dynamically get PR information 2025-06-01T20:56:27.5430712Z """ 2025-06-01T20:56:27.5431196Z github_api = f"https://api.github.com/repos/{github_repo}" 2025-06-01T20:56:27.5431807Z headers = { 2025-06-01T20:56:27.5432253Z "Accept": "application/vnd.github.v3+json", 2025-06-01T20:56:27.5432838Z "Authorization": f"token {github_token}", 2025-06-01T20:56:27.5433361Z } 2025-06-01T20:56:27.5433771Z json_response: dict[str, Any] = download_json( 2025-06-01T20:56:27.5434375Z url=f"{github_api}/issues/{pr_number}", 2025-06-01T20:56:27.5435001Z headers=headers, 2025-06-01T20:56:27.5435414Z ) 2025-06-01T20:56:27.5435611Z 2025-06-01T20:56:27.5435784Z if not json_response: 2025-06-01T20:56:27.5436326Z log.warning(f"Failed to get the labels for #{pr_number}") 2025-06-01T20:56:27.5436932Z return {} 2025-06-01T20:56:27.5437174Z 2025-06-01T20:56:27.5437459Z return json_response 2025-06-01T20:56:27.5437746Z 2025-06-01T20:56:27.5437752Z 2025-06-01T20:56:27.5438126Z def get_labels(github_repo: str, github_token: str, pr_number: int) -> set[str]: 2025-06-01T20:56:27.5438837Z """ 2025-06-01T20:56:27.5439336Z Dynamically get the latest list of labels from the pull request 2025-06-01T20:56:27.5439979Z """ 2025-06-01T20:56:27.5440436Z pr_info = get_pr_info(github_repo, github_token, pr_number) 2025-06-01T20:56:27.5441038Z return { 2025-06-01T20:56:27.5441596Z label.get("name") for label in pr_info.get("labels", []) if label.get("name") 2025-06-01T20:56:27.5442277Z } 2025-06-01T20:56:27.5442472Z 2025-06-01T20:56:27.5442478Z 2025-06-01T20:56:27.5442631Z def main() -> None: 2025-06-01T20:56:27.5443037Z args = parse_args() 2025-06-01T20:56:27.5443312Z 2025-06-01T20:56:27.5443517Z runner_label_prefix = DEFAULT_LABEL_PREFIX 2025-06-01T20:56:27.5443911Z 2025-06-01T20:56:27.5444114Z # Check if the PR is opt-out 2025-06-01T20:56:27.5444694Z if args.pr_number: 2025-06-01T20:56:27.5445328Z labels = get_labels(args.github_repo, args.github_token, int(args.pr_number)) 2025-06-01T20:56:27.5446180Z if OPT_OUT_LABEL in labels: 2025-06-01T20:56:27.5446662Z log.info( 2025-06-01T20:56:27.5447315Z f"Opt-out runner determinator because #{args.pr_number} has {OPT_OUT_LABEL} label" 2025-06-01T20:56:27.5448066Z ) 2025-06-01T20:56:27.5448588Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.5449237Z sys.exit() 2025-06-01T20:56:27.5449498Z 2025-06-01T20:56:27.5449641Z try: 2025-06-01T20:56:27.5450058Z rollout_state = get_rollout_state_from_issue( 2025-06-01T20:56:27.5450737Z args.github_token, args.github_issue_repo, args.github_issue 2025-06-01T20:56:27.5451353Z ) 2025-06-01T20:56:27.5451552Z 2025-06-01T20:56:27.5451736Z username = get_potential_pr_author( 2025-06-01T20:56:27.5452278Z args.github_token, 2025-06-01T20:56:27.5452733Z args.github_repo, 2025-06-01T20:56:27.5453195Z args.github_actor, 2025-06-01T20:56:27.5453670Z args.github_ref_type, 2025-06-01T20:56:27.5454157Z args.github_branch, 2025-06-01T20:56:27.5454698Z ) 2025-06-01T20:56:27.5454907Z 2025-06-01T20:56:27.5455166Z is_canary = args.github_repo == "pytorch/pytorch-canary" 2025-06-01T20:56:27.5455622Z 2025-06-01T20:56:27.5455820Z runner_label_prefix = get_runner_prefix( 2025-06-01T20:56:27.5456373Z rollout_state, 2025-06-01T20:56:27.5456846Z (args.github_issue_owner, username), 2025-06-01T20:56:27.5457390Z args.github_branch, 2025-06-01T20:56:27.5457865Z args.eligible_experiments, 2025-06-01T20:56:27.5458397Z args.opt_out_experiments, 2025-06-01T20:56:27.5458884Z is_canary, 2025-06-01T20:56:27.5459291Z ) 2025-06-01T20:56:27.5459502Z 2025-06-01T20:56:27.5459676Z except Exception as e: 2025-06-01T20:56:27.5460128Z log.error( 2025-06-01T20:56:27.5460752Z f"Failed to get issue. Defaulting to Meta runners and no experiments. Exception: {e}" 2025-06-01T20:56:27.5461498Z ) 2025-06-01T20:56:27.5461713Z 2025-06-01T20:56:27.5462022Z set_github_output(GH_OUTPUT_KEY_LABEL_TYPE, runner_label_prefix) 2025-06-01T20:56:27.5462513Z 2025-06-01T20:56:27.5462519Z 2025-06-01T20:56:27.5462678Z if __name__ == "__main__": 2025-06-01T20:56:27.5463113Z main() 2025-06-01T20:56:27.5463331Z 2025-06-01T20:56:27.5547206Z ##[group]Run python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:27.5548047Z python3 -m pip install urllib3==1.26.18 PyGithub==2.3.0 2025-06-01T20:56:27.5599530Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:27.5599979Z env: 2025-06-01T20:56:27.5600550Z GITHUB_TOKEN: *** 2025-06-01T20:56:27.5600937Z ISSUE_NUMBER: 5132 2025-06-01T20:56:27.5601353Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:27.5601977Z ISSUE_OWNER: 2025-06-01T20:56:27.5602348Z CHECK_EXPERIMENTS: 2025-06-01T20:56:27.5602748Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:27.5603135Z PR_NUMBER: 2025-06-01T20:56:27.5603484Z ##[endgroup] 2025-06-01T20:56:27.9270782Z Defaulting to user installation because normal site-packages is not writeable 2025-06-01T20:56:28.2549675Z Collecting urllib3==1.26.18 2025-06-01T20:56:28.2867386Z Downloading urllib3-1.26.18-py2.py3-none-any.whl.metadata (48 kB) 2025-06-01T20:56:28.3067032Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 48.9/48.9 kB 4.2 MB/s eta 0:00:00 2025-06-01T20:56:28.3314511Z Collecting PyGithub==2.3.0 2025-06-01T20:56:28.3360051Z Downloading PyGithub-2.3.0-py3-none-any.whl.metadata (3.8 kB) 2025-06-01T20:56:28.3783864Z Collecting pynacl>=1.4.0 (from PyGithub==2.3.0) 2025-06-01T20:56:28.3811895Z 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-06-01T20:56:28.3870612Z Requirement already satisfied: requests>=2.14.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (2.31.0) 2025-06-01T20:56:28.3886655Z 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-06-01T20:56:28.3901310Z Requirement already satisfied: typing-extensions>=4.0.0 in /usr/lib/python3/dist-packages (from PyGithub==2.3.0) (4.10.0) 2025-06-01T20:56:28.4155449Z Collecting Deprecated (from PyGithub==2.3.0) 2025-06-01T20:56:28.4184246Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl.metadata (5.7 kB) 2025-06-01T20:56:28.4436781Z 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-06-01T20:56:28.6106443Z Collecting cffi>=1.4.1 (from pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:28.6140658Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB) 2025-06-01T20:56:28.7158593Z Collecting wrapt<2,>=1.10 (from Deprecated->PyGithub==2.3.0) 2025-06-01T20:56:28.7192732Z 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-06-01T20:56:28.7379154Z Collecting pycparser (from cffi>=1.4.1->pynacl>=1.4.0->PyGithub==2.3.0) 2025-06-01T20:56:28.7407445Z Downloading pycparser-2.22-py3-none-any.whl.metadata (943 bytes) 2025-06-01T20:56:28.7665897Z Downloading urllib3-1.26.18-py2.py3-none-any.whl (143 kB) 2025-06-01T20:56:28.7730686Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 143.8/143.8 kB 28.8 MB/s eta 0:00:00 2025-06-01T20:56:28.7761535Z Downloading PyGithub-2.3.0-py3-none-any.whl (354 kB) 2025-06-01T20:56:28.7845050Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 354.4/354.4 kB 55.9 MB/s eta 0:00:00 2025-06-01T20:56:28.7876832Z Downloading PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (856 kB) 2025-06-01T20:56:28.7977974Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 856.7/856.7 kB 102.1 MB/s eta 0:00:00 2025-06-01T20:56:28.8008072Z Downloading Deprecated-1.2.18-py2.py3-none-any.whl (10.0 kB) 2025-06-01T20:56:28.8071219Z Downloading cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (479 kB) 2025-06-01T20:56:28.8146178Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 479.4/479.4 kB 87.8 MB/s eta 0:00:00 2025-06-01T20:56:28.8178675Z 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-06-01T20:56:28.8222087Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 89.2/89.2 kB 27.3 MB/s eta 0:00:00 2025-06-01T20:56:28.8256022Z Downloading pycparser-2.22-py3-none-any.whl (117 kB) 2025-06-01T20:56:28.8309645Z ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.6/117.6 kB 28.3 MB/s eta 0:00:00 2025-06-01T20:56:29.1154147Z Installing collected packages: wrapt, urllib3, pycparser, Deprecated, cffi, pynacl, PyGithub 2025-06-01T20:56:29.6576211Z 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-06-01T20:56:29.7370942Z ##[group]Run curr_branch="main" 2025-06-01T20:56:29.7371307Z curr_branch="main" 2025-06-01T20:56:29.7371581Z curr_ref_type="branch" 2025-06-01T20:56:29.7371884Z echo "Current branch is '$curr_branch'" 2025-06-01T20:56:29.7372190Z  2025-06-01T20:56:29.7372427Z python3 runner_determinator.py \ 2025-06-01T20:56:29.7372764Z  --github-token "$GITHUB_TOKEN" \ 2025-06-01T20:56:29.7373096Z  --github-issue "$ISSUE_NUMBER" \ 2025-06-01T20:56:29.7373408Z  --github-branch "$curr_branch" \ 2025-06-01T20:56:29.7373736Z  --github-actor "$TRIGGERING_ACTOR" \ 2025-06-01T20:56:29.7374087Z  --github-issue-owner "$ISSUE_OWNER" \ 2025-06-01T20:56:29.7374428Z  --github-ref-type "$curr_ref_type" \ 2025-06-01T20:56:29.7374988Z  --github-repo "$GITHUB_REPOSITORY" \ 2025-06-01T20:56:29.7375403Z  --eligible-experiments "$CHECK_EXPERIMENTS" \ 2025-06-01T20:56:29.7375857Z  --opt-out-experiments "$OPT_OUT_EXPERIMENTS" \ 2025-06-01T20:56:29.7376208Z  --pr-number "${PR_NUMBER}" 2025-06-01T20:56:29.7428354Z shell: /usr/bin/bash -e {0} 2025-06-01T20:56:29.7428636Z env: 2025-06-01T20:56:29.7429309Z GITHUB_TOKEN: *** 2025-06-01T20:56:29.7429551Z ISSUE_NUMBER: 5132 2025-06-01T20:56:29.7429809Z TRIGGERING_ACTOR: pytorchmergebot 2025-06-01T20:56:29.7430106Z ISSUE_OWNER: 2025-06-01T20:56:29.7430346Z CHECK_EXPERIMENTS: 2025-06-01T20:56:29.7430587Z OPT_OUT_EXPERIMENTS: 2025-06-01T20:56:29.7430833Z PR_NUMBER: 2025-06-01T20:56:29.7431041Z ##[endgroup] 2025-06-01T20:56:29.7501455Z Current branch is 'main' 2025-06-01T20:56:31.5119862Z INFO : Based on rollout percentage of 30%, enabling experiment lf. 2025-06-01T20:56:31.5121570Z INFO : Branch main is an exception branch. Not enabling experiment ephemeral. 2025-06-01T20:56:31.5122557Z INFO : Branch main is an exception branch. Not enabling experiment wincanary. 2025-06-01T20:56:31.5123293Z INFO : Setting output: label-type='lf.' 2025-06-01T20:56:31.5437328Z Evaluate and set job outputs 2025-06-01T20:56:31.5444071Z Set output 'label-type' 2025-06-01T20:56:31.5446510Z Cleaning up orphan processes