652 lines
23 KiB
Python
652 lines
23 KiB
Python
# coding: utf-8
|
|
import glob
|
|
import logging
|
|
import multiprocessing
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
from unittest import mock
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
import ray
|
|
import ray._private.gcs_utils as gcs_utils
|
|
import ray._private.ray_constants as ray_constants
|
|
import ray._private.utils
|
|
import ray.cluster_utils
|
|
import ray.util.accelerators
|
|
from ray._common.test_utils import wait_for_condition
|
|
from ray._common.utils import RESOURCE_CONSTRAINT_PREFIX
|
|
from ray._private.test_utils import rocksdb_gcs_test_enabled
|
|
from ray.dashboard import k8s_utils
|
|
from ray.runtime_env import RuntimeEnv
|
|
|
|
import psutil
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def test_export_after_shutdown(ray_start_regular):
|
|
# This test checks that we can use actor and remote function definitions
|
|
# across multiple Ray sessions.
|
|
|
|
@ray.remote
|
|
def f():
|
|
pass
|
|
|
|
@ray.remote
|
|
class Actor:
|
|
def method(self):
|
|
pass
|
|
|
|
ray.get(f.remote())
|
|
a = Actor.remote()
|
|
ray.get(a.method.remote())
|
|
|
|
ray.shutdown()
|
|
|
|
# Start Ray and use the remote function and actor again.
|
|
ray.init(num_cpus=1)
|
|
ray.get(f.remote())
|
|
a = Actor.remote()
|
|
ray.get(a.method.remote())
|
|
|
|
ray.shutdown()
|
|
|
|
# Start Ray again and make sure that these definitions can be exported from
|
|
# workers.
|
|
ray.init(num_cpus=2)
|
|
|
|
@ray.remote
|
|
def export_definitions_from_worker(remote_function, actor_class):
|
|
ray.get(remote_function.remote())
|
|
actor_handle = actor_class.remote()
|
|
ray.get(actor_handle.method.remote())
|
|
|
|
ray.get(export_definitions_from_worker.remote(f, Actor))
|
|
|
|
|
|
def test_invalid_unicode_in_worker_log(shutdown_only):
|
|
info = ray.init(num_cpus=1)
|
|
|
|
logs_dir = os.path.join(info["session_dir"], "logs")
|
|
|
|
# Wait till first worker log file is created.
|
|
while True:
|
|
log_file_paths = glob.glob(f"{logs_dir}/worker*.out")
|
|
if len(log_file_paths) == 0:
|
|
time.sleep(0.2)
|
|
else:
|
|
break
|
|
|
|
with open(log_file_paths[0], "wb") as f:
|
|
f.write(b"\xe5abc\nline2\nline3\n")
|
|
f.write(b"\xe5abc\nline2\nline3\n")
|
|
f.write(b"\xe5abc\nline2\nline3\n")
|
|
f.flush()
|
|
|
|
# Wait till the log monitor reads the file.
|
|
time.sleep(1.0)
|
|
|
|
# Make sure that nothing has died.
|
|
assert ray._private.services.remaining_processes_alive()
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
rocksdb_gcs_test_enabled(),
|
|
reason=(
|
|
"Starts a second local Ray cluster while the fixture's head is alive; "
|
|
"both inherit RAY_gcs_storage_path and collide on the same RocksDB "
|
|
"directory (single-writer LOCK). Redis tolerates this via server-side "
|
|
"multiplexing; RocksDB cannot."
|
|
),
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"ray_start_cluster",
|
|
[
|
|
{
|
|
"num_cpus": 0,
|
|
"num_nodes": 1,
|
|
"do_init": False,
|
|
}
|
|
],
|
|
indirect=True,
|
|
)
|
|
def test_ray_address_environment_variable(ray_start_cluster):
|
|
address = ray_start_cluster.address
|
|
# In this test we use zero CPUs to distinguish between starting a local
|
|
# ray cluster and connecting to an existing one.
|
|
|
|
# Make sure we connect to an existing cluster if
|
|
# RAY_ADDRESS is set to the cluster address.
|
|
os.environ["RAY_ADDRESS"] = address
|
|
ray.init()
|
|
assert "CPU" not in ray._private.state.cluster_resources()
|
|
ray.shutdown()
|
|
del os.environ["RAY_ADDRESS"]
|
|
|
|
# Make sure we connect to an existing cluster if
|
|
# RAY_ADDRESS is set to "auto".
|
|
os.environ["RAY_ADDRESS"] = "auto"
|
|
ray.init()
|
|
assert "CPU" not in ray._private.state.cluster_resources()
|
|
ray.shutdown()
|
|
del os.environ["RAY_ADDRESS"]
|
|
|
|
# Prefer `address` parameter to the `RAY_ADDRESS` environment variable,
|
|
# when `address` is not `auto`.
|
|
os.environ["RAY_ADDRESS"] = "test"
|
|
ray.init(address=address)
|
|
assert "CPU" not in ray._private.state.cluster_resources()
|
|
ray.shutdown()
|
|
del os.environ["RAY_ADDRESS"]
|
|
|
|
# Make sure we connect to the existing cluster with on args and RAY_ADDRESS
|
|
# is not set.
|
|
ray.init()
|
|
assert "CPU" not in ray._private.state.cluster_resources()
|
|
ray.shutdown()
|
|
|
|
# Make sure we start a new cluster if "local" is explicitly passed.
|
|
# is not set.
|
|
ray.init(address="local")
|
|
assert "CPU" in ray._private.state.cluster_resources()
|
|
ray.shutdown()
|
|
|
|
|
|
def test_ray_resources_environment_variable(shutdown_only):
|
|
os.environ[
|
|
ray_constants.RESOURCES_ENVIRONMENT_VARIABLE
|
|
] = '{"custom1":1, "custom2":2, "CPU":3}'
|
|
ray.init(resources={"custom1": 3, "custom3": 3})
|
|
|
|
cluster_resources = ray.cluster_resources()
|
|
print(cluster_resources)
|
|
assert cluster_resources["custom1"] == 1
|
|
assert cluster_resources["custom2"] == 2
|
|
assert cluster_resources["custom3"] == 3
|
|
assert cluster_resources["CPU"] == 3
|
|
|
|
|
|
def test_ray_labels_environment_variables(shutdown_only):
|
|
os.environ[
|
|
ray_constants.LABELS_ENVIRONMENT_VARIABLE
|
|
] = '{"custom1":"1", "custom2":"2"}'
|
|
ray.init(labels={"custom1": "3", "custom3": "3"})
|
|
|
|
node_info = ray.nodes()[0]
|
|
assert node_info["Labels"]["custom1"] == "1"
|
|
assert node_info["Labels"]["custom2"] == "2"
|
|
assert node_info["Labels"]["custom3"] == "3"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"accelerator_type",
|
|
[ray.util.accelerators.NVIDIA_TESLA_V100, ray.util.accelerators.AWS_NEURON_CORE],
|
|
)
|
|
def test_accelerator_type_api(accelerator_type, shutdown_only):
|
|
resource_name = f"{RESOURCE_CONSTRAINT_PREFIX}{accelerator_type}"
|
|
ray.init(num_cpus=4, resources={resource_name: 1})
|
|
|
|
quantity = 1
|
|
|
|
@ray.remote(accelerator_type=accelerator_type)
|
|
def decorated_func(quantity):
|
|
wait_for_condition(lambda: ray.available_resources()[resource_name] < quantity)
|
|
return True
|
|
|
|
assert ray.get(decorated_func.remote(quantity))
|
|
|
|
def via_options_func(quantity):
|
|
wait_for_condition(lambda: ray.available_resources()[resource_name] < quantity)
|
|
return True
|
|
|
|
assert ray.get(
|
|
ray.remote(via_options_func)
|
|
.options(accelerator_type=accelerator_type)
|
|
.remote(quantity)
|
|
)
|
|
|
|
@ray.remote(accelerator_type=accelerator_type)
|
|
class DecoratedActor:
|
|
def __init__(self):
|
|
pass
|
|
|
|
def initialized(self):
|
|
pass
|
|
|
|
class ActorWithOptions:
|
|
def __init__(self):
|
|
pass
|
|
|
|
def initialized(self):
|
|
pass
|
|
|
|
decorated_actor = DecoratedActor.remote()
|
|
# Avoid a race condition where the actor hasn't been initialized and
|
|
# claimed the resources yet.
|
|
ray.get(decorated_actor.initialized.remote())
|
|
wait_for_condition(lambda: ray.available_resources()[resource_name] < quantity)
|
|
|
|
quantity = ray.available_resources()[resource_name]
|
|
with_options = (
|
|
ray.remote(ActorWithOptions).options(accelerator_type=accelerator_type).remote()
|
|
)
|
|
ray.get(with_options.initialized.remote())
|
|
wait_for_condition(lambda: ray.available_resources()[resource_name] < quantity)
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="not relevant for windows")
|
|
def test_get_system_memory():
|
|
# cgroups v1, set
|
|
with tempfile.NamedTemporaryFile("w") as memory_limit_file:
|
|
memory_limit_file.write("100")
|
|
memory_limit_file.flush()
|
|
assert (
|
|
ray._common.utils.get_system_memory(
|
|
memory_limit_filename=memory_limit_file.name,
|
|
memory_limit_filename_v2="__does_not_exist__",
|
|
)
|
|
== 100
|
|
)
|
|
|
|
# cgroups v1, high
|
|
with tempfile.NamedTemporaryFile("w") as memory_limit_file:
|
|
memory_limit_file.write(str(2**64))
|
|
memory_limit_file.flush()
|
|
psutil_memory_in_bytes = psutil.virtual_memory().total
|
|
assert (
|
|
ray._common.utils.get_system_memory(
|
|
memory_limit_filename=memory_limit_file.name,
|
|
memory_limit_filename_v2="__does_not_exist__",
|
|
)
|
|
== psutil_memory_in_bytes
|
|
)
|
|
# cgroups v2, set
|
|
with tempfile.NamedTemporaryFile("w") as memory_max_file:
|
|
memory_max_file.write("100\n")
|
|
memory_max_file.flush()
|
|
assert (
|
|
ray._common.utils.get_system_memory(
|
|
memory_limit_filename="__does_not_exist__",
|
|
memory_limit_filename_v2=memory_max_file.name,
|
|
)
|
|
== 100
|
|
)
|
|
|
|
# cgroups v2, not set
|
|
with tempfile.NamedTemporaryFile("w") as memory_max_file:
|
|
memory_max_file.write("max")
|
|
memory_max_file.flush()
|
|
psutil_memory_in_bytes = psutil.virtual_memory().total
|
|
assert (
|
|
ray._common.utils.get_system_memory(
|
|
memory_limit_filename="__does_not_exist__",
|
|
memory_limit_filename_v2=memory_max_file.name,
|
|
)
|
|
== psutil_memory_in_bytes
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("in_k8s", [True, False])
|
|
@pytest.mark.parametrize("env_disable", [True, False])
|
|
@pytest.mark.parametrize("override_disable", [True, False])
|
|
@pytest.mark.parametrize("got_docker_cpus", [True, False])
|
|
def test_get_num_cpus(
|
|
in_k8s: bool,
|
|
env_disable: bool,
|
|
override_disable: bool,
|
|
got_docker_cpus: bool,
|
|
monkeypatch,
|
|
):
|
|
"""Tests
|
|
- Conditions under which ray._private.utils.get_num_cpus logs a warning about
|
|
docker.
|
|
- Fallback to multiprocessing.cpu_count if there's no docker count available.
|
|
"""
|
|
# Shouldn't get the log warning if we're in K8s, the env variable is set,
|
|
# the flag arg to get_num_cpus is set, or getting docker cpus fails.
|
|
# Otherwise, should get the log message.
|
|
should_not_log = any([in_k8s, env_disable, override_disable, not got_docker_cpus])
|
|
expected_warning = (
|
|
"Detecting docker specified CPUs. In "
|
|
"previous versions of Ray, CPU detection in containers "
|
|
"was incorrect. Please ensure that Ray has enough CPUs "
|
|
"allocated. As a temporary workaround to revert to the "
|
|
"prior behavior, set "
|
|
"`RAY_USE_MULTIPROCESSING_CPU_COUNT=1` as an env var "
|
|
"before starting Ray. Set the env var: "
|
|
"`RAY_DISABLE_DOCKER_CPU_WARNING=1` to mute this warning."
|
|
)
|
|
if got_docker_cpus:
|
|
mock_get_docker_cpus = mock.Mock(return_value=128)
|
|
else:
|
|
mock_get_docker_cpus = mock.Mock(side_effect=Exception())
|
|
|
|
if in_k8s:
|
|
monkeypatch.setenv("KUBERNETES_SERVICE_HOST", 1)
|
|
else:
|
|
try:
|
|
monkeypatch.delenv("KUBERNETES_SERVICE_HOST")
|
|
except KeyError:
|
|
pass
|
|
|
|
with mock.patch.multiple(
|
|
"ray._private.utils",
|
|
_get_docker_cpus=mock_get_docker_cpus,
|
|
ENV_DISABLE_DOCKER_CPU_WARNING=env_disable,
|
|
logger=mock.DEFAULT,
|
|
) as mocks:
|
|
num_cpus = ray._private.utils.get_num_cpus(override_disable)
|
|
|
|
if got_docker_cpus:
|
|
# Got the docker count of 128 CPUs in the giant mock container.
|
|
assert num_cpus == 128
|
|
else:
|
|
# Failed to get docker count and fell back to multiprocessing count.
|
|
assert num_cpus == multiprocessing.cpu_count()
|
|
|
|
if should_not_log:
|
|
mocks["logger"].warning.assert_not_called()
|
|
else:
|
|
mocks["logger"].warning.assert_called_with(expected_warning)
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="not relevant for windows")
|
|
def test_detect_docker_cpus():
|
|
# No limits set
|
|
with tempfile.NamedTemporaryFile("w") as quota_file, tempfile.NamedTemporaryFile(
|
|
"w"
|
|
) as period_file, tempfile.NamedTemporaryFile("w") as cpuset_file:
|
|
quota_file.write("-1")
|
|
period_file.write("100000")
|
|
cpuset_file.write("0-63")
|
|
quota_file.flush()
|
|
period_file.flush()
|
|
cpuset_file.flush()
|
|
assert (
|
|
ray._private.utils._get_docker_cpus(
|
|
cpu_quota_file_name=quota_file.name,
|
|
cpu_period_file_name=period_file.name,
|
|
cpuset_file_name=cpuset_file.name,
|
|
)
|
|
== 64
|
|
)
|
|
|
|
# No cpuset used
|
|
with tempfile.NamedTemporaryFile("w") as quota_file, tempfile.NamedTemporaryFile(
|
|
"w"
|
|
) as period_file, tempfile.NamedTemporaryFile("w") as cpuset_file:
|
|
quota_file.write("-1")
|
|
period_file.write("100000")
|
|
cpuset_file.write("0-10,20,50-63")
|
|
quota_file.flush()
|
|
period_file.flush()
|
|
cpuset_file.flush()
|
|
assert (
|
|
ray._private.utils._get_docker_cpus(
|
|
cpu_quota_file_name=quota_file.name,
|
|
cpu_period_file_name=period_file.name,
|
|
cpuset_file_name=cpuset_file.name,
|
|
)
|
|
== 26
|
|
)
|
|
|
|
# Quota set
|
|
with tempfile.NamedTemporaryFile("w") as quota_file, tempfile.NamedTemporaryFile(
|
|
"w"
|
|
) as period_file, tempfile.NamedTemporaryFile("w") as cpuset_file:
|
|
quota_file.write("42")
|
|
period_file.write("100")
|
|
cpuset_file.write("0-63")
|
|
quota_file.flush()
|
|
period_file.flush()
|
|
cpuset_file.flush()
|
|
assert (
|
|
ray._private.utils._get_docker_cpus(
|
|
cpu_quota_file_name=quota_file.name,
|
|
cpu_period_file_name=period_file.name,
|
|
cpuset_file_name=cpuset_file.name,
|
|
)
|
|
== 0.42
|
|
)
|
|
|
|
# cgroups v2, cpu_quota set
|
|
with tempfile.NamedTemporaryFile("w") as cpu_max_file:
|
|
cpu_max_file.write("200000 100000")
|
|
cpu_max_file.flush()
|
|
assert (
|
|
ray._private.utils._get_docker_cpus(
|
|
cpu_quota_file_name="nope",
|
|
cpu_period_file_name="give_up",
|
|
cpuset_file_name="lose_hope",
|
|
cpu_max_file_name=cpu_max_file.name,
|
|
)
|
|
== 2.0
|
|
)
|
|
|
|
# cgroups v2, cpu_quota unset
|
|
with tempfile.NamedTemporaryFile("w") as cpu_max_file:
|
|
cpu_max_file.write("max 100000")
|
|
cpu_max_file.flush()
|
|
assert (
|
|
ray._private.utils._get_docker_cpus(
|
|
cpu_quota_file_name="nope",
|
|
cpu_period_file_name="give_up",
|
|
cpuset_file_name="lose_hope",
|
|
cpu_max_file_name=cpu_max_file.name,
|
|
)
|
|
is None
|
|
)
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
sys.platform.startswith("win"), reason="No need to test on Windows."
|
|
)
|
|
@pytest.mark.parametrize("use_cgroups_v2", [True, False])
|
|
def test_k8s_cpu(use_cgroups_v2: bool):
|
|
"""Test all the functions in dashboard/k8s_utils.py.
|
|
Also test ray._private.utils.get_num_cpus when running in a K8s pod.
|
|
Files were obtained from within a K8s pod with 2 CPU request, CPU limit
|
|
unset, with 1 CPU of stress applied.
|
|
"""
|
|
|
|
# Some experimentally-obtained K8S CPU usage files for use in test_k8s_cpu.
|
|
PROCSTAT1 = """cpu 2945022 98 3329420 148744854 39522 0 118587 0 0 0
|
|
cpu0 370299 14 413841 18589778 5304 0 15288 0 0 0
|
|
cpu1 378637 10 414414 18589275 5283 0 14731 0 0 0
|
|
cpu2 367328 8 420914 18590974 4844 0 14416 0 0 0
|
|
cpu3 368378 11 423720 18572899 4948 0 14394 0 0 0
|
|
cpu4 369051 13 414615 18607285 4736 0 14383 0 0 0
|
|
cpu5 362958 10 415984 18576655 4590 0 16614 0 0 0
|
|
cpu6 362536 13 414430 18605197 4785 0 14353 0 0 0
|
|
cpu7 365833 15 411499 18612787 5028 0 14405 0 0 0
|
|
intr 1000694027 125 0 0 39 154 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1028 0 2160913 0 2779605 8 0 3981333 3665198 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
ctxt 1574979439
|
|
btime 1615208601
|
|
processes 857411
|
|
procs_running 6
|
|
procs_blocked 0
|
|
softirq 524311775 0 230142964 27143 63542182 0 0 171 74042767 0 156556548
|
|
""" # noqa
|
|
|
|
PROCSTAT2 = """cpu 2945152 98 3329436 148745483 39522 0 118587 0 0 0
|
|
cpu0 370399 14 413841 18589778 5304 0 15288 0 0 0
|
|
cpu1 378647 10 414415 18589362 5283 0 14731 0 0 0
|
|
cpu2 367329 8 420916 18591067 4844 0 14416 0 0 0
|
|
cpu3 368381 11 423724 18572989 4948 0 14395 0 0 0
|
|
cpu4 369052 13 414618 18607374 4736 0 14383 0 0 0
|
|
cpu5 362968 10 415986 18576741 4590 0 16614 0 0 0
|
|
cpu6 362537 13 414432 18605290 4785 0 14353 0 0 0
|
|
cpu7 365836 15 411502 18612878 5028 0 14405 0 0 0
|
|
intr 1000700905 125 0 0 39 154 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1028 0 2160923 0 2779605 8 0 3981353 3665218 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
|
ctxt 1574988760
|
|
btime 1615208601
|
|
processes 857411
|
|
procs_running 4
|
|
procs_blocked 0
|
|
softirq 524317451 0 230145523 27143 63542930 0 0 171 74043232 0 156558452
|
|
""" # noqa
|
|
|
|
CPUACCTUSAGE1 = "2268980984000"
|
|
|
|
CPUACCTUSAGE2 = "2270120061999"
|
|
|
|
CPU_STAT_1 = """usage_usec 2268980984
|
|
user_usec 5673216
|
|
system_usec 794353
|
|
nr_periods 168
|
|
nr_throttled 6
|
|
throttled_usec 638117
|
|
"""
|
|
|
|
CPU_STAT_2 = """usage_usec 2270120061
|
|
user_usec 5673216
|
|
system_usec 794353
|
|
nr_periods 168
|
|
nr_throttled 6
|
|
throttled_usec 638117
|
|
"""
|
|
|
|
cpu_file, cpu_v2_file, proc_stat_file = [
|
|
tempfile.NamedTemporaryFile("w+") for _ in range(3)
|
|
]
|
|
cpu_file.write(CPUACCTUSAGE1)
|
|
cpu_v2_file.write(CPU_STAT_1)
|
|
proc_stat_file.write(PROCSTAT1)
|
|
for file in cpu_file, cpu_v2_file, proc_stat_file:
|
|
file.flush()
|
|
|
|
if use_cgroups_v2:
|
|
# Should get a file not found for cpuacctusage if on cgroups v2
|
|
cpu_usage_file = "NO_SUCH_FILE"
|
|
else:
|
|
# If using cgroups v1, use the temp file we've just made
|
|
cpu_usage_file = cpu_file.name
|
|
with mock.patch(
|
|
"ray._private.utils.os.environ", {"KUBERNETES_SERVICE_HOST": "host"}
|
|
), mock.patch("ray.dashboard.k8s_utils.CPU_USAGE_PATH", cpu_usage_file), mock.patch(
|
|
"ray.dashboard.k8s_utils.CPU_USAGE_PATH_V2", cpu_v2_file.name
|
|
), mock.patch(
|
|
"ray.dashboard.k8s_utils.PROC_STAT_PATH", proc_stat_file.name
|
|
), mock.patch(
|
|
# get_num_cpus is tested elsewhere
|
|
"ray.dashboard.k8s_utils.get_num_cpus",
|
|
mock.Mock(return_value=2),
|
|
), mock.patch(
|
|
# Reset this global variable between tests.
|
|
"ray.dashboard.k8s_utils.last_system_usage",
|
|
None,
|
|
):
|
|
# Validate mocks:
|
|
# Confirm CPU_USAGE_PATH is found with cgroups v2, but not with v2.
|
|
from ray.dashboard.k8s_utils import CPU_USAGE_PATH
|
|
|
|
if use_cgroups_v2:
|
|
with pytest.raises(FileNotFoundError):
|
|
print(open(CPU_USAGE_PATH).read())
|
|
else:
|
|
print(open(CPU_USAGE_PATH).read())
|
|
|
|
# Test helpers
|
|
assert k8s_utils._cpu_usage() == 2268980984000
|
|
assert k8s_utils._system_usage() == 1551775030000000
|
|
assert k8s_utils._host_num_cpus() == 8
|
|
|
|
# No delta for first computation, return 0.
|
|
assert k8s_utils.cpu_percent() == 0.0
|
|
|
|
# Write new usage info obtained after 1 sec wait.
|
|
for file in cpu_file, cpu_v2_file, proc_stat_file:
|
|
file.truncate(0)
|
|
file.seek(0)
|
|
cpu_file.write(CPUACCTUSAGE2)
|
|
cpu_v2_file.write(CPU_STAT_2)
|
|
proc_stat_file.write(PROCSTAT2)
|
|
for file in cpu_file, cpu_v2_file, proc_stat_file:
|
|
file.flush()
|
|
|
|
# Files were extracted under 1 CPU of load on a 2 CPU pod
|
|
assert 50 < k8s_utils.cpu_percent() < 60
|
|
|
|
|
|
def test_sync_job_config(shutdown_only):
|
|
runtime_env = {"env_vars": {"key": "value"}}
|
|
|
|
ray.init(
|
|
job_config=ray.job_config.JobConfig(
|
|
runtime_env=runtime_env,
|
|
)
|
|
)
|
|
|
|
# Check that the job config is synchronized at the driver side.
|
|
job_config = ray._private.worker.global_worker.core_worker.get_job_config()
|
|
job_runtime_env = RuntimeEnv.deserialize(
|
|
job_config.runtime_env_info.serialized_runtime_env
|
|
)
|
|
assert job_runtime_env.env_vars() == runtime_env["env_vars"]
|
|
|
|
@ray.remote
|
|
def get_job_config():
|
|
job_config = ray._private.worker.global_worker.core_worker.get_job_config()
|
|
return job_config.SerializeToString()
|
|
|
|
# Check that the job config is synchronized at the worker side.
|
|
job_config = gcs_utils.JobConfig()
|
|
job_config.ParseFromString(ray.get(get_job_config.remote()))
|
|
job_runtime_env = RuntimeEnv.deserialize(
|
|
job_config.runtime_env_info.serialized_runtime_env
|
|
)
|
|
assert job_runtime_env.env_vars() == runtime_env["env_vars"]
|
|
|
|
|
|
def test_duplicated_arg(ray_start_cluster):
|
|
cluster = ray_start_cluster
|
|
cluster.add_node(num_cpus=1)
|
|
ray.init(address=cluster.address)
|
|
|
|
@ray.remote
|
|
def task_with_dup_arg(*args):
|
|
return sum(args)
|
|
|
|
# Basic verification.
|
|
arr = np.ones(1 * 1024 * 1024, dtype=np.uint8) # 1MB
|
|
ref = ray.put(arr)
|
|
assert np.array_equal(
|
|
ray.get(task_with_dup_arg.remote(ref, ref, ref)), sum([arr, arr, arr])
|
|
)
|
|
|
|
# Make sure it works when it is mixed with other args.
|
|
ref2 = ray.put(arr)
|
|
assert np.array_equal(
|
|
ray.get(task_with_dup_arg.remote(ref, ref2, ref)), sum([arr, arr, arr])
|
|
)
|
|
|
|
# Test complicated scenario with multi nodes.
|
|
cluster.add_node(num_cpus=1, resources={"worker_1": 1})
|
|
cluster.add_node(num_cpus=1, resources={"worker_2": 1})
|
|
cluster.wait_for_nodes()
|
|
|
|
@ray.remote
|
|
def create_remote_ref(arr):
|
|
return ray.put(arr)
|
|
|
|
@ray.remote
|
|
def task_with_dup_arg_ref(*args):
|
|
args = ray.get(list(args))
|
|
return sum(args)
|
|
|
|
ref1 = create_remote_ref.options(resources={"worker_1": 1}).remote(arr)
|
|
ref2 = create_remote_ref.options(resources={"worker_2": 1}).remote(arr)
|
|
ref3 = create_remote_ref.remote(arr)
|
|
np.array_equal(
|
|
ray.get(task_with_dup_arg_ref.remote(ref1, ref2, ref3, ref1, ref2, ref3)),
|
|
sum([arr] * 6),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(pytest.main(["-sv", __file__]))
|