Files
ray-project--ray/python/ray/tests/test_actor_state_metrics.py
2026-07-13 13:17:40 +08:00

327 lines
8.1 KiB
Python

import asyncio
import sys
import time
from collections import defaultdict
from typing import Dict
import pytest
import ray
from ray._common.test_utils import (
PrometheusTimeseries,
run_string_as_driver,
wait_for_condition,
)
from ray._private.test_utils import raw_metric_timeseries
from ray._private.worker import RayContext
from ray.util.state import list_actors
_SYSTEM_CONFIG = {
"metrics_report_interval_ms": 200,
}
def actors_by_state(info: RayContext, timeseries: PrometheusTimeseries) -> Dict:
res = raw_metric_timeseries(info, timeseries)
actors_info = defaultdict(int)
if "ray_actors" in res:
for sample in res["ray_actors"]:
actors_info[sample.labels["State"]] += sample.value
for k, v in actors_info.copy().items():
if v == 0:
del actors_info[k]
print(f"Actors by state: {actors_info}")
return actors_info
def actors_by_name(info: RayContext, timeseries: PrometheusTimeseries) -> Dict:
res = raw_metric_timeseries(info, timeseries)
actors_info = defaultdict(int)
if "ray_actors" in res:
for sample in res["ray_actors"]:
actors_info[sample.labels["Name"]] += sample.value
for k, v in actors_info.copy().items():
if v == 0:
del actors_info[k]
print(f"Actors by name: {actors_info}")
return actors_info
def test_basic_states(shutdown_only):
info = ray.init(num_cpus=3, _system_config=_SYSTEM_CONFIG)
@ray.remote(num_cpus=1)
class Actor:
def ping(self):
pass
def sleep(self):
time.sleep(999)
def get(self):
@ray.remote
def sleep():
time.sleep(999)
ray.get(sleep.remote())
def wait(self):
@ray.remote
def sleep():
time.sleep(999)
ray.wait([sleep.remote()])
a = Actor.remote()
b = Actor.remote()
c = Actor.remote()
ray.get(a.ping.remote())
ray.get(b.ping.remote())
ray.get(c.ping.remote())
d = Actor.remote()
timeseries = PrometheusTimeseries()
# Test creation states.
expected = {
"ALIVE": 3,
"ALIVE_IDLE": 3,
"PENDING_CREATION": 1,
}
wait_for_condition(
lambda: actors_by_state(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
# Test running states.
a.sleep.remote()
b.get.remote()
c.wait.remote()
expected = {
"ALIVE": 3,
"ALIVE_RUNNING_TASKS": 3,
"PENDING_CREATION": 1,
}
wait_for_condition(
lambda: actors_by_state(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
del d
def test_destroy_actors(shutdown_only):
info = ray.init(num_cpus=3, _system_config=_SYSTEM_CONFIG)
@ray.remote(num_cpus=1)
class Actor:
def ping(self):
pass
a = Actor.remote()
b = Actor.remote()
c = Actor.remote()
del a
del b
timeseries = PrometheusTimeseries()
expected = {
"ALIVE": 1,
"ALIVE_IDLE": 1,
"DEAD": 2,
}
wait_for_condition(
lambda: actors_by_state(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
del c
def test_destroy_actors_from_driver(monkeypatch, shutdown_only):
with monkeypatch.context() as m:
# Dead actors are not cached.
m.setenv("RAY_maximum_gcs_destroyed_actor_cached_count", 5)
m.setenv("RAY_WORKER_TIMEOUT_S", 5)
driver = """
import ray
ray.init("auto")
@ray.remote(num_cpus=0)
class Actor:
def ready(self):
pass
actors = [Actor.remote() for _ in range(10)]
ray.get([actor.ready.remote() for actor in actors])
"""
info = ray.init(num_cpus=3, _system_config=_SYSTEM_CONFIG)
output = run_string_as_driver(driver)
print(output)
timeseries = PrometheusTimeseries()
expected = {
"DEAD": 10,
}
wait_for_condition(
lambda: expected.items() <= actors_by_state(info, timeseries).items(),
timeout=20,
retry_interval_ms=500,
)
"""
Make sure even after the actor entries are deleted from GCS by GC
the metrics are correct.
"""
# Wait until the state API returns the # of actors are 0
# becasue entries are GC'ed by GCS.
wait_for_condition(lambda: len(list_actors()) == 5)
# DEAD count shouldn't be changed.
wait_for_condition(
lambda: expected.items() <= actors_by_state(info, timeseries).items(),
timeout=20,
retry_interval_ms=500,
)
def test_dep_wait(shutdown_only):
info = ray.init(num_cpus=3, _system_config=_SYSTEM_CONFIG)
@ray.remote
def sleep():
time.sleep(999)
@ray.remote(num_cpus=1)
class Actor:
def __init__(self, x):
pass
a = Actor.remote(sleep.remote())
timeseries = PrometheusTimeseries()
expected = {
"DEPENDENCIES_UNREADY": 1,
}
wait_for_condition(
lambda: actors_by_state(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
del a
def test_async_actor(shutdown_only):
info = ray.init(num_cpus=3, _system_config=_SYSTEM_CONFIG)
@ray.remote
def sleep():
time.sleep(999)
@ray.remote(max_concurrency=30)
class AsyncActor:
async def sleep(self):
await asyncio.sleep(300)
async def do_get(self):
await ray.get(sleep.remote())
a = AsyncActor.remote()
a.sleep.remote()
timeseries = PrometheusTimeseries()
expected = {
"ALIVE": 1,
"ALIVE_RUNNING_TASKS": 1,
}
wait_for_condition(
lambda: actors_by_state(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
# Test that this transitions the entire actor to reporting IN_RAY_GET state.
a.do_get.remote()
a.do_get.remote()
expected = {
"ALIVE": 1,
"ALIVE_RUNNING_TASKS": 1,
}
wait_for_condition(
lambda: actors_by_state(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
del a
@ray.remote(num_cpus=1)
class Actor1:
def sleep(self):
time.sleep(999)
@ray.remote(num_cpus=1)
class Actor2:
def sleep(self):
time.sleep(999)
def test_tracking_by_name(shutdown_only):
info = ray.init(num_cpus=3, _system_config=_SYSTEM_CONFIG)
a = Actor1.remote()
b = Actor2.remote()
timeseries = PrometheusTimeseries()
expected = {
# one reported by gcs as ALIVE
# another reported by core worker as IDLE
"Actor1": 2,
"Actor2": 2,
}
wait_for_condition(
lambda: actors_by_name(info, timeseries) == expected,
timeout=20,
retry_interval_ms=500,
)
del a
del b
def test_get_all_actors_info(shutdown_only):
ray.init(num_cpus=2, include_dashboard=True)
@ray.remote(num_cpus=1)
class Actor:
def ping(self):
pass
actor_1 = Actor.remote()
actor_2 = Actor.remote()
ray.get([actor_1.ping.remote(), actor_2.ping.remote()], timeout=5)
actors_info = list_actors(detail=True)
assert len(actors_info) == 2
job_id_hex = ray.get_runtime_context().get_job_id()
actors_info = list_actors(filters=[("job_id", "=", job_id_hex)], detail=True)
assert len(actors_info) == 2
actors_info = list_actors(
filters=[("job_id", "=", ray.JobID.from_int(100).hex())], detail=True
)
assert len(actors_info) == 0
# To filter actors by state
actor_3 = Actor.remote()
wait_for_condition(
lambda: len(list_actors(filters=[("state", "=", "PENDING_CREATION")])) == 1
)
assert actor_3._actor_id.hex() in list(
map(
lambda s: s.actor_id,
list_actors(filters=[("state", "=", "PENDING_CREATION")]),
)
)
with pytest.raises(ray.util.state.exception.RayStateApiException):
actors_info = list_actors(filters=[("state", "=", "UNKONWN_STATE")])
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))