mongo/buildscripts/evergreen_task_timeout.py

167 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Determine the timeout value a task should use in evergreen."""
import argparse
import math
import sys
from datetime import timedelta
from typing import Optional
import yaml
COMMIT_QUEUE_ALIAS = "__commit_queue"
UNITTEST_TASK = "run_unittests"
COMMIT_QUEUE_TIMEOUT = timedelta(minutes=40)
DEFAULT_REQUIRED_BUILD_TIMEOUT = timedelta(hours=1, minutes=20)
DEFAULT_NON_REQUIRED_BUILD_TIMEOUT = timedelta(hours=2)
# 2x the longest "run tests" phase for unittests as of c9bf1dbc9cc46e497b2f12b2d6685ef7348b0726,
# which is 5 mins 47 secs, excluding outliers below
UNITTESTS_TIMEOUT = timedelta(minutes=12)
SPECIFIC_TASK_OVERRIDES = {
"linux-64-debug": {"auth": timedelta(minutes=60)},
"enterprise-windows-all-feature-flags-suggested": {
"replica_sets_jscore_passthrough": timedelta(hours=3),
"replica_sets_update_v1_oplog_jscore_passthrough": timedelta(hours=2, minutes=30),
},
"enterprise-windows-required": {
"replica_sets_jscore_passthrough": timedelta(hours=3),
"replica_sets_update_v1_oplog_jscore_passthrough": timedelta(hours=2, minutes=30),
},
"enterprise-windows-inmem": {"replica_sets_jscore_passthrough": timedelta(hours=3), },
"enterprise-windows": {"replica_sets_jscore_passthrough": timedelta(hours=3), },
"windows-debug-suggested": {
"replica_sets_initsync_jscore_passthrough": timedelta(hours=2, minutes=30),
"replica_sets_jscore_passthrough": timedelta(hours=2, minutes=30),
"replica_sets_update_v1_oplog_jscore_passthrough": timedelta(hours=2, minutes=30),
},
"windows": {
"replica_sets": timedelta(hours=3),
"replica_sets_jscore_passthrough": timedelta(hours=2, minutes=30),
},
"ubuntu1804-debug-suggested": {"replica_sets_jscore_passthrough": timedelta(hours=3), },
"ubuntu1804-sbe-yielding-debug": {
"concurrency_replication_ubsan": timedelta(hours=2, minutes=30),
},
"enterprise-rhel-80-64-bit-coverage": {
"replica_sets_jscore_passthrough": timedelta(hours=2, minutes=30),
},
"macos": {"replica_sets_jscore_passthrough": timedelta(hours=2, minutes=30), },
"enterprise-macos": {"replica_sets_jscore_passthrough": timedelta(hours=2, minutes=30), },
# unittests outliers
# repeated execution runs a suite 10 times
"linux-64-repeated-execution": {UNITTEST_TASK: 10 * UNITTESTS_TIMEOUT},
# some of the a/ub/t san variants need a little extra time
"enterprise-ubuntu2004-debug-tsan": {UNITTEST_TASK: 2 * UNITTESTS_TIMEOUT},
"ubuntu1804-asan": {UNITTEST_TASK: 2 * UNITTESTS_TIMEOUT},
"ubuntu1804-ubsan": {UNITTEST_TASK: 2 * UNITTESTS_TIMEOUT},
"ubuntu1804-debug-asan": {UNITTEST_TASK: 2 * UNITTESTS_TIMEOUT},
"ubuntu1804-debug-aubsan-lite": {UNITTEST_TASK: 2 * UNITTESTS_TIMEOUT},
"ubuntu1804-debug-ubsan": {UNITTEST_TASK: 2 * UNITTESTS_TIMEOUT},
}
def _is_required_build_variant(build_variant: str) -> bool:
"""
Determine if the given build variants is a required build variant.
:param build_variant: Name of build variant to check.
:return: True if the given build variant is required.
"""
return build_variant.endswith("-required")
def _has_override(variant: str, task_name: str) -> bool:
"""
Determine if the given task has a timeout override.
:param variant: Build Variant task is running on.
:param task_name: Task to check.
:return: True if override exists for task.
"""
return variant in SPECIFIC_TASK_OVERRIDES and task_name in SPECIFIC_TASK_OVERRIDES[variant]
def determine_timeout(task_name: str, variant: str, idle_timeout: Optional[timedelta] = None,
exec_timeout: Optional[timedelta] = None, evg_alias: str = '') -> timedelta:
"""
Determine what exec timeout should be used.
:param task_name: Name of task being run.
:param variant: Name of build variant being run.
:param idle_timeout: Idle timeout if specified.
:param exec_timeout: Override to use for exec_timeout or 0 if no override.
:param evg_alias: Evergreen alias running the task.
:return: Exec timeout to use for running task.
"""
determined_timeout = DEFAULT_NON_REQUIRED_BUILD_TIMEOUT
if exec_timeout and exec_timeout.total_seconds() != 0:
determined_timeout = exec_timeout
elif task_name == UNITTEST_TASK and not _has_override(variant, task_name):
determined_timeout = UNITTESTS_TIMEOUT
elif evg_alias == COMMIT_QUEUE_ALIAS:
determined_timeout = COMMIT_QUEUE_TIMEOUT
elif _has_override(variant, task_name):
determined_timeout = SPECIFIC_TASK_OVERRIDES[variant][task_name]
elif _is_required_build_variant(variant):
determined_timeout = DEFAULT_REQUIRED_BUILD_TIMEOUT
# The timeout needs to be at least as large as the idle timeout.
if idle_timeout and determined_timeout.total_seconds() < idle_timeout.total_seconds():
return idle_timeout
return determined_timeout
def output_timeout(task_timeout: timedelta, output_file: Optional[str]) -> None:
"""
Output timeout configuration to the specified location.
:param task_timeout: Timeout to output.
:param output_file: Location of output file to write.
"""
output = {
"exec_timeout_secs": math.ceil(task_timeout.total_seconds()),
}
if output_file:
with open(output_file, "w") as outfile:
yaml.dump(output, stream=outfile, default_flow_style=False)
yaml.dump(output, stream=sys.stdout, default_flow_style=False)
def main():
"""Determine the timeout value a task should use in evergreen."""
parser = argparse.ArgumentParser(description=main.__doc__)
parser.add_argument("--task-name", dest="task", required=True, help="Task being executed.")
parser.add_argument("--build-variant", dest="variant", required=True,
help="Build variant task is being executed on.")
parser.add_argument("--evg-alias", dest="evg_alias", required=True,
help="Evergreen alias used to trigger build.")
parser.add_argument("--timeout", dest="timeout", type=int, help="Timeout to use (in sec).")
parser.add_argument("--exec-timeout", dest="exec_timeout", type=int,
help="Exec timeout ot use (in sec).")
parser.add_argument("--out-file", dest="outfile", help="File to write configuration to.")
options = parser.parse_args()
timeout_override = timedelta(seconds=options.timeout) if options.timeout else None
exec_timeout_override = timedelta(
seconds=options.exec_timeout) if options.exec_timeout else None
task_timeout = determine_timeout(options.task, options.variant, timeout_override,
exec_timeout_override, options.evg_alias)
output_timeout(task_timeout, options.outfile)
if __name__ == "__main__":
main()