869 lines
21 KiB
Python
869 lines
21 KiB
Python
import copy
|
|
import multiprocessing
|
|
import sys
|
|
from collections import defaultdict
|
|
|
|
import pytest
|
|
|
|
import ray
|
|
from ray._common.test_utils import (
|
|
PrometheusTimeseries,
|
|
run_string_as_driver,
|
|
wait_for_condition,
|
|
)
|
|
from ray._private.metrics_agent import RAY_WORKER_TIMEOUT_S
|
|
from ray._private.test_utils import (
|
|
raw_metric_timeseries,
|
|
run_string_as_driver_nonblocking,
|
|
wait_for_assertion,
|
|
wait_for_dashboard_agent_available,
|
|
)
|
|
|
|
METRIC_CONFIG = {
|
|
"_system_config": {
|
|
"metrics_report_interval_ms": 100,
|
|
}
|
|
}
|
|
|
|
SLOW_METRIC_CONFIG = {
|
|
"_system_config": {
|
|
"metrics_report_interval_ms": 3000,
|
|
}
|
|
}
|
|
|
|
|
|
def tasks_by_state(info, timeseries: PrometheusTimeseries, flush: bool = False) -> dict:
|
|
if flush:
|
|
timeseries.flush()
|
|
return tasks_breakdown(info, lambda s: s.labels["State"], timeseries)
|
|
|
|
|
|
def tasks_by_name_and_state(info, timeseries: PrometheusTimeseries) -> dict:
|
|
return tasks_breakdown(
|
|
info, lambda s: (s.labels["Name"], s.labels["State"]), timeseries
|
|
)
|
|
|
|
|
|
def tasks_by_all(info, timeseries: PrometheusTimeseries) -> dict:
|
|
return tasks_breakdown(
|
|
info,
|
|
lambda s: (s.labels["Name"], s.labels["State"], s.labels["IsRetry"]),
|
|
timeseries,
|
|
)
|
|
|
|
|
|
def tasks_breakdown(info, key_fn, timeseries: PrometheusTimeseries) -> dict:
|
|
res = raw_metric_timeseries(info, timeseries)
|
|
if "ray_tasks" in res:
|
|
breakdown = defaultdict(int)
|
|
for sample in res["ray_tasks"]:
|
|
key = key_fn(sample)
|
|
breakdown[key] += sample.value
|
|
if breakdown[key] == 0:
|
|
del breakdown[key]
|
|
print("Task label breakdown: {}".format(breakdown))
|
|
return breakdown
|
|
else:
|
|
return {}
|
|
|
|
|
|
# TODO(ekl) in all these tests, we use run_string_as_driver_nonblocking to work around
|
|
# stats reporting issues if Ray is repeatedly restarted in unit tests.
|
|
def test_task_basic(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
|
|
driver = """
|
|
import time
|
|
|
|
import ray
|
|
from ray._common.test_utils import wait_for_condition
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def a():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def b():
|
|
time.sleep(999)
|
|
|
|
@ray.remote(num_cpus=3)
|
|
def c():
|
|
time.sleep(999)
|
|
|
|
refs = [a.remote(), b.remote()]
|
|
wait_for_condition(
|
|
lambda: ray.available_resources().get("CPU", 0) == 0,
|
|
)
|
|
|
|
ray.get(refs + [c.remote() for _ in range(8)])
|
|
"""
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
timeseries = PrometheusTimeseries()
|
|
expected = {
|
|
"RUNNING": 2.0,
|
|
"PENDING_NODE_ASSIGNMENT": 8.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
assert tasks_by_name_and_state(info, timeseries) == {
|
|
("a", "RUNNING"): 1.0,
|
|
("b", "RUNNING"): 1.0,
|
|
("c", "PENDING_NODE_ASSIGNMENT"): 8.0,
|
|
}
|
|
proc.kill()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Flaky on Windows.")
|
|
def test_task_custom_name_metrics(shutdown_only):
|
|
"""Verify that custom task names set via .options(name=...) are used in metrics.
|
|
|
|
This tests that RUNNING tasks use the custom name consistently with
|
|
FINISHED/FAILED tasks. Previously there was a bug where RUNNING metrics used
|
|
the function name (FunctionDescriptor->CallString()) but FINISHED/FAILED used
|
|
the custom name (TaskSpec::GetName()).
|
|
"""
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def my_function():
|
|
time.sleep(999)
|
|
|
|
# Submit tasks with custom names
|
|
a = [my_function.options(name="custom_task_name").remote() for _ in range(4)]
|
|
ray.get(a)
|
|
"""
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
timeseries = PrometheusTimeseries()
|
|
|
|
# Verify that RUNNING tasks use the custom name, not the function name.
|
|
# With 2 CPUs, 2 tasks should be running and 2 should be pending.
|
|
expected = {
|
|
("custom_task_name", "RUNNING"): 2.0,
|
|
("custom_task_name", "PENDING_NODE_ASSIGNMENT"): 2.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_name_and_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
|
|
# Verify the original function name is NOT used in metrics
|
|
breakdown = tasks_by_name_and_state(info, timeseries)
|
|
assert (
|
|
"my_function",
|
|
"RUNNING",
|
|
) not in breakdown, "RUNNING tasks should use custom name, not function name"
|
|
assert (
|
|
"my_function",
|
|
"PENDING_NODE_ASSIGNMENT",
|
|
) not in breakdown, "PENDING tasks should use custom name, not function name"
|
|
|
|
proc.kill()
|
|
|
|
|
|
def test_task_job_ids(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote(num_cpus=0)
|
|
def {func_name}():
|
|
time.sleep(999)
|
|
a = [{func_name}.remote() for _ in range(1)]
|
|
ray.get(a)
|
|
"""
|
|
# We make sure the task name is unique for each job so that we can distinguish the metric sample by task name across jobs.
|
|
procs = [
|
|
run_string_as_driver_nonblocking(driver.format(func_name=f"f_{i}"))
|
|
for i in range(3)
|
|
]
|
|
|
|
expected = {
|
|
"RUNNING": 3.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
|
|
# Check we have three jobs reporting "RUNNING".
|
|
metrics = raw_metric_timeseries(info, timeseries)
|
|
jobs_at_state = defaultdict(set)
|
|
for sample in metrics["ray_tasks"]:
|
|
jobs_at_state[sample.labels["State"]].add(sample.labels["JobId"])
|
|
print("Jobs at state: {}".format(jobs_at_state))
|
|
assert len(jobs_at_state["RUNNING"]) == 3, jobs_at_state
|
|
|
|
for proc in procs:
|
|
proc.kill()
|
|
|
|
|
|
def test_task_nested(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote(num_cpus=0)
|
|
def wrapper():
|
|
@ray.remote
|
|
def a():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def b():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def c():
|
|
time.sleep(999)
|
|
|
|
ray.get([a.remote(), b.remote()] + [c.remote() for _ in range(8)])
|
|
|
|
w = wrapper.remote()
|
|
ray.get(w)
|
|
"""
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
|
|
expected = {
|
|
"RUNNING": 2.0,
|
|
"RUNNING_IN_RAY_GET": 1.0,
|
|
"PENDING_NODE_ASSIGNMENT": 8.0,
|
|
}
|
|
|
|
def check_task_state():
|
|
assert tasks_by_state(info, timeseries) == expected
|
|
|
|
wait_for_assertion(
|
|
check_task_state,
|
|
timeout=30,
|
|
retry_interval_ms=2000,
|
|
)
|
|
assert tasks_by_name_and_state(info, timeseries) == {
|
|
("wrapper", "RUNNING_IN_RAY_GET"): 1.0,
|
|
("a", "RUNNING"): 1.0,
|
|
("b", "RUNNING"): 1.0,
|
|
("c", "PENDING_NODE_ASSIGNMENT"): 8.0,
|
|
}
|
|
proc.kill()
|
|
|
|
|
|
def test_task_nested_wait(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote(num_cpus=0)
|
|
def wrapper():
|
|
@ray.remote
|
|
def a():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def b():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def c():
|
|
time.sleep(999)
|
|
|
|
ray.wait([a.remote(), b.remote()] + [c.remote() for _ in range(8)])
|
|
|
|
w = wrapper.remote()
|
|
ray.get(w)
|
|
"""
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
|
|
expected = {
|
|
"RUNNING": 2.0,
|
|
"RUNNING_IN_RAY_WAIT": 1.0,
|
|
"PENDING_NODE_ASSIGNMENT": 8.0,
|
|
}
|
|
|
|
def check_task_state():
|
|
assert tasks_by_state(info, timeseries) == expected
|
|
|
|
wait_for_assertion(
|
|
check_task_state,
|
|
timeout=30,
|
|
retry_interval_ms=2000,
|
|
)
|
|
assert tasks_by_name_and_state(info, timeseries) == {
|
|
("wrapper", "RUNNING_IN_RAY_WAIT"): 1.0,
|
|
("a", "RUNNING"): 1.0,
|
|
("b", "RUNNING"): 1.0,
|
|
("c", "PENDING_NODE_ASSIGNMENT"): 8.0,
|
|
}
|
|
proc.kill()
|
|
|
|
|
|
def driver_for_test_task_fetch_args(head_info):
|
|
ray.init("auto")
|
|
timeseries = PrometheusTimeseries()
|
|
|
|
@ray.remote(resources={"worker": 1})
|
|
def task1():
|
|
return [1] * 1024 * 1024
|
|
|
|
@ray.remote(resources={"head": 1})
|
|
def task2(obj):
|
|
pass
|
|
|
|
o1 = task1.remote()
|
|
o2 = task2.remote(o1)
|
|
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(head_info, timeseries).get("PENDING_ARGS_FETCH", 0.0)
|
|
== 1.0
|
|
)
|
|
|
|
ray.cancel(o2)
|
|
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(head_info, timeseries).get("PENDING_ARGS_FETCH", 0.0)
|
|
== 0.0
|
|
)
|
|
|
|
|
|
def test_task_fetch_args(ray_start_cluster):
|
|
cluster = ray_start_cluster
|
|
cluster.add_node(
|
|
resources={"head": 1},
|
|
_system_config={
|
|
"metrics_report_interval_ms": 100,
|
|
"testing_asio_delay_us": "ObjectManagerService.grpc_server.Pull=5000000000:5000000000", # noqa: E501
|
|
},
|
|
)
|
|
head_info = ray.init(address=cluster.address)
|
|
cluster.add_node(resources={"worker": 1})
|
|
cluster.wait_for_nodes()
|
|
|
|
multiprocessing.set_start_method("spawn")
|
|
p = multiprocessing.Process(
|
|
target=driver_for_test_task_fetch_args, args=(head_info,)
|
|
)
|
|
p.start()
|
|
p.join()
|
|
assert p.exitcode == 0
|
|
|
|
|
|
def test_task_wait_on_deps(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def f():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def g(x):
|
|
time.sleep(999)
|
|
|
|
x = f.remote()
|
|
a = [g.remote(x) for _ in range(5)]
|
|
ray.get(a)
|
|
"""
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
"RUNNING": 1.0,
|
|
"PENDING_ARGS_AVAIL": 5.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
assert tasks_by_name_and_state(info, timeseries) == {
|
|
("f", "RUNNING"): 1.0,
|
|
("g", "PENDING_ARGS_AVAIL"): 5.0,
|
|
}
|
|
proc.kill()
|
|
|
|
|
|
def test_actor_tasks_queued(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
class F:
|
|
def f(self):
|
|
time.sleep(999)
|
|
|
|
def g(self):
|
|
pass
|
|
|
|
a = F.remote()
|
|
[a.g.remote() for _ in range(10)]
|
|
[a.f.remote() for _ in range(1)] # Further tasks should be blocked on this one.
|
|
z = [a.g.remote() for _ in range(9)]
|
|
ray.get(z)
|
|
"""
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
("F.__init__", "FINISHED"): 1.0,
|
|
("F.g", "FINISHED"): 10.0,
|
|
("F.f", "RUNNING"): 1.0,
|
|
("F.g", "SUBMITTED_TO_WORKER"): 9.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_name_and_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
def test_task_finish(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def f():
|
|
return "ok"
|
|
|
|
@ray.remote
|
|
def g():
|
|
assert False
|
|
|
|
f.remote()
|
|
g.remote()
|
|
time.sleep(999)
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
"FAILED": 1.0,
|
|
"FINISHED": 1.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
assert tasks_by_name_and_state(info, timeseries) == {
|
|
("g", "FAILED"): 1.0,
|
|
("f", "FINISHED"): 1.0,
|
|
}
|
|
proc.kill()
|
|
|
|
|
|
def test_task_retry(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def sleep():
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
class Phaser:
|
|
def __init__(self):
|
|
self.i = 0
|
|
|
|
def inc(self):
|
|
self.i += 1
|
|
if self.i < 3:
|
|
raise ValueError("First two tries will fail")
|
|
|
|
phaser = Phaser.remote()
|
|
|
|
@ray.remote(retry_exceptions=True, max_retries=3)
|
|
def f():
|
|
ray.get(phaser.inc.remote())
|
|
ray.get(sleep.remote())
|
|
|
|
f.remote()
|
|
time.sleep(999)
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
("sleep", "RUNNING", "0"): 1.0,
|
|
("f", "FAILED", "0"): 1.0,
|
|
("f", "FAILED", "1"): 1.0,
|
|
("f", "RUNNING_IN_RAY_GET", "1"): 1.0,
|
|
("Phaser.__init__", "FINISHED", "0"): 1.0,
|
|
("Phaser.inc", "FINISHED", "0"): 1.0,
|
|
("Phaser.inc", "FAILED", "0"): 2.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: expected.items() <= tasks_by_all(info, timeseries).items(),
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Flaky on Windows. Timing out.")
|
|
def test_actor_task_retry(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import os
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
class Phaser:
|
|
def __init__(self):
|
|
self.i = 0
|
|
|
|
def inc(self):
|
|
self.i += 1
|
|
if self.i < 3:
|
|
raise ValueError("First two tries will fail")
|
|
|
|
phaser = Phaser.remote()
|
|
|
|
@ray.remote(max_restarts=10, max_task_retries=10)
|
|
class F:
|
|
def f(self):
|
|
try:
|
|
ray.get(phaser.inc.remote())
|
|
except Exception:
|
|
print("RESTART")
|
|
os._exit(1)
|
|
|
|
f = F.remote()
|
|
ray.get(f.f.remote())
|
|
time.sleep(999)
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
("F.__init__", "FINISHED", "0"): 1.0,
|
|
("F.f", "FAILED", "0"): 1.0,
|
|
("F.f", "FAILED", "1"): 1.0,
|
|
("F.f", "FINISHED", "1"): 1.0,
|
|
("Phaser.__init__", "FINISHED", "0"): 1.0,
|
|
("Phaser.inc", "FINISHED", "0"): 1.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: expected.items() <= tasks_by_all(info, timeseries).items(),
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Flaky on Windows.")
|
|
def test_task_failure(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
import os
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote(max_retries=0)
|
|
def f():
|
|
print("RUNNING FAILING TASK")
|
|
os._exit(1)
|
|
|
|
@ray.remote
|
|
def g():
|
|
assert False
|
|
|
|
f.remote()
|
|
g.remote()
|
|
time.sleep(999)
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
"FAILED": 2.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
def test_concurrent_actor_tasks(shutdown_only):
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import asyncio
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote(max_concurrency=30)
|
|
class A:
|
|
async def f(self):
|
|
await asyncio.sleep(300)
|
|
|
|
a = A.remote()
|
|
ray.get([a.f.remote() for _ in range(40)])
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
"RUNNING": 30.0,
|
|
"SUBMITTED_TO_WORKER": 10.0,
|
|
"FINISHED": 1.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Flaky on Windows.")
|
|
def test_metrics_export_now(shutdown_only, ray_start_cluster):
|
|
cluster = ray_start_cluster
|
|
cluster.add_node(
|
|
**SLOW_METRIC_CONFIG,
|
|
num_cpus=2,
|
|
)
|
|
wait_for_dashboard_agent_available(cluster)
|
|
info = ray.init(address=cluster.address)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def {func_name}():
|
|
pass
|
|
a = [{func_name}.remote() for _ in range(10)]
|
|
ray.get(a)
|
|
"""
|
|
|
|
# If force export at process death is broken, we won't see the recently completed
|
|
# tasks from the drivers. We also make sure the task name is unique for each job so
|
|
# that we can distinguish the metric sample by task name across jobs.
|
|
for i in range(10):
|
|
print("Run job", i)
|
|
run_string_as_driver(driver.format(func_name=f"f_{i}"))
|
|
tasks_by_state(info, timeseries)
|
|
|
|
expected = {
|
|
"FINISHED": 100.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "darwin", reason="Flaky on macos")
|
|
def test_pull_manager_stats(shutdown_only):
|
|
info = ray.init(num_cpus=2, object_store_memory=100_000_000, **METRIC_CONFIG)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import time
|
|
import numpy as np
|
|
|
|
ray.init("auto")
|
|
|
|
# Spill a lot of 10MiB objects. The object store is 100MiB, so pull manager will
|
|
# only be able to pull ~9 total into memory at once, including running tasks.
|
|
buf = []
|
|
for _ in range(100):
|
|
buf.append(ray.put(np.ones(10 * 1024 * 1024, dtype=np.uint8)))
|
|
|
|
@ray.remote
|
|
def a(x):
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def b(x):
|
|
time.sleep(999)
|
|
|
|
@ray.remote
|
|
def c(x):
|
|
time.sleep(999)
|
|
|
|
ray.get([a.remote(buf[0]), b.remote(buf[1])] + [c.remote(x) for x in buf[2:]])
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
|
|
# This test is non-deterministic since pull bundles can sometimes end up fallback
|
|
# allocated. This leads to slightly more objects pulled than you'd expect.
|
|
def close_to_expected(stats):
|
|
# A scheduled task can momentarily sit in SUBMITTED_TO_WORKER (lease granted,
|
|
# waiting on the worker to fetch its spilled arg) before it reports RUNNING.
|
|
# Under the object store pressure this test creates, that transition can be
|
|
# slow, so count SUBMITTED_TO_WORKER as running to avoid flakiness.
|
|
running = stats.get("RUNNING", 0) + stats.get("SUBMITTED_TO_WORKER", 0)
|
|
assert running == 2, stats
|
|
assert 7 <= stats["PENDING_NODE_ASSIGNMENT"] <= 17, stats
|
|
assert 81 <= stats["PENDING_OBJ_STORE_MEM_AVAIL"] <= 91, stats
|
|
assert set(stats.keys()).issubset(
|
|
{
|
|
"RUNNING",
|
|
"SUBMITTED_TO_WORKER",
|
|
"PENDING_NODE_ASSIGNMENT",
|
|
"PENDING_OBJ_STORE_MEM_AVAIL",
|
|
}
|
|
), stats
|
|
assert sum(stats.values()) == 100, stats
|
|
return True
|
|
|
|
wait_for_condition(
|
|
lambda: close_to_expected(tasks_by_state(info, timeseries)),
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Flaky on Windows.")
|
|
def test_stale_view_cleanup_when_job_exits(monkeypatch, shutdown_only):
|
|
timeseries = PrometheusTimeseries()
|
|
with monkeypatch.context() as m:
|
|
m.setenv(RAY_WORKER_TIMEOUT_S, 5)
|
|
info = ray.init(num_cpus=2, **METRIC_CONFIG)
|
|
print(info)
|
|
|
|
driver = """
|
|
import ray
|
|
import time
|
|
import numpy as np
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
def g():
|
|
time.sleep(999)
|
|
|
|
ray.get(g.remote())
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
"RUNNING": 1.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
|
|
proc.kill()
|
|
print("Killing a driver.")
|
|
expected = {}
|
|
wait_for_condition(
|
|
lambda: tasks_by_state(info, timeseries, flush=True) == expected,
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="Flaky on Windows. Timing out.")
|
|
def test_metrics_batch(shutdown_only):
|
|
"""Verify metrics_report_batch_size works correctly without data loss."""
|
|
config_copy = copy.deepcopy(METRIC_CONFIG)
|
|
config_copy["_system_config"].update({"metrics_report_batch_size": 1})
|
|
info = ray.init(num_cpus=2, **config_copy)
|
|
timeseries = PrometheusTimeseries()
|
|
driver = """
|
|
import ray
|
|
import os
|
|
import time
|
|
|
|
ray.init("auto")
|
|
|
|
@ray.remote
|
|
class Phaser:
|
|
def __init__(self):
|
|
self.i = 0
|
|
|
|
def inc(self):
|
|
self.i += 1
|
|
if self.i < 3:
|
|
raise ValueError("First two tries will fail")
|
|
|
|
phaser = Phaser.remote()
|
|
|
|
@ray.remote(max_restarts=10, max_task_retries=10)
|
|
class F:
|
|
def f(self):
|
|
try:
|
|
ray.get(phaser.inc.remote())
|
|
except Exception:
|
|
print("RESTART")
|
|
os._exit(1)
|
|
|
|
f = F.remote()
|
|
ray.get(f.f.remote())
|
|
time.sleep(999)
|
|
"""
|
|
|
|
proc = run_string_as_driver_nonblocking(driver)
|
|
expected = {
|
|
("F.__init__", "FINISHED", "0"): 1.0,
|
|
("F.f", "FAILED", "0"): 1.0,
|
|
("F.f", "FAILED", "1"): 1.0,
|
|
("F.f", "FINISHED", "1"): 1.0,
|
|
("Phaser.__init__", "FINISHED", "0"): 1.0,
|
|
("Phaser.inc", "FINISHED", "0"): 1.0,
|
|
}
|
|
wait_for_condition(
|
|
lambda: expected.items() <= tasks_by_all(info, timeseries).items(),
|
|
timeout=20,
|
|
retry_interval_ms=500,
|
|
)
|
|
proc.kill()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-sv", __file__]))
|