Files
2026-07-13 13:17:40 +08:00

259 lines
7.8 KiB
Python

import logging
import threading
import typing
from abc import ABC, abstractmethod
from typing import Any, List, Optional
import ray
from ray.data._internal.execution.operators.sub_progress import SubProgressBarMixin
from ray.data._internal.progress.utils import truncate_operator_name
if typing.TYPE_CHECKING:
from ray.data._internal.execution.resource_manager import ResourceManager
from ray.data._internal.execution.streaming_executor_state import OpState, Topology
from ray.types import ObjectRef
logger = logging.getLogger(__name__)
# Used a signal to cancel execution.
_canceled_threads = set()
_canceled_threads_lock = threading.Lock()
def _extract_num_rows(result: Any) -> int:
"""Extract the number of rows from a result object.
Args:
result: The result object from which to extract the number of rows.
Returns:
The number of rows, defaulting to 1 if it cannot be determined.
"""
if hasattr(result, "num_rows"):
return result.num_rows
elif hasattr(result, "__len__"):
# For output is DataFrame,i.e. sort_sample
return len(result)
else:
return 1
class BaseProgressBar(ABC):
"""Base class to define a progress bar."""
def block_until_complete(self, remaining: List["ObjectRef"]) -> None:
t = threading.current_thread()
while remaining:
done, remaining = ray.wait(
remaining, num_returns=len(remaining), fetch_local=False, timeout=0.1
)
total_rows_processed = 0
for _, result in zip(done, ray.get(done)):
num_rows = _extract_num_rows(result)
total_rows_processed += num_rows
self.update(total_rows_processed)
with _canceled_threads_lock:
if t in _canceled_threads:
break
def fetch_until_complete(self, refs: List["ObjectRef"]) -> List[Any]:
ref_to_result = {}
remaining = refs
t = threading.current_thread()
# Triggering fetch_local redundantly for the same object is slower.
# We only need to trigger the fetch_local once for each object,
# raylet will persist these fetch requests even after ray.wait returns.
# See https://github.com/ray-project/ray/issues/30375.
fetch_local = True
while remaining:
done, remaining = ray.wait(
remaining,
num_returns=len(remaining),
fetch_local=fetch_local,
timeout=0.1,
)
if fetch_local:
fetch_local = False
total_rows_processed = 0
for ref, result in zip(done, ray.get(done)):
ref_to_result[ref] = result
num_rows = _extract_num_rows(result)
total_rows_processed += num_rows
self.update(total_rows_processed)
with _canceled_threads_lock:
if t in _canceled_threads:
break
return [ref_to_result[ref] for ref in refs]
@abstractmethod
def set_description(self, name: str) -> None:
...
@abstractmethod
def get_description(self) -> str:
...
@abstractmethod
def update(self, increment: int = 0, total: Optional[int] = None) -> None:
...
def refresh(self):
pass
def close(self):
pass
class BaseExecutionProgressManager(ABC):
"""Base Data Execution Progress Display Manager"""
# If the name/description of the progress bar exceeds this length,
# it will be truncated.
MAX_NAME_LENGTH = 100
# Total progress refresh rate (update interval in scheduling step)
# refer to `streaming_executor.py::StreamingExecutor::_scheduling_loop_step`
TOTAL_PROGRESS_REFRESH_EVERY_N_STEPS = 50
@abstractmethod
def __init__(
self,
dataset_id: str,
topology: "Topology",
show_op_progress: bool,
verbose_progress: bool,
):
"""Initialize the progress manager, create all necessary progress bars
and sub-progress bars for the given topology. Sub-progress bars are
created for operators that implement the SubProgressBarMixin.
Args:
dataset_id: id of Dataset
topology: operation topology built via `build_streaming_topology`
show_op_progress: whether to show individual operator progress
(only for non-AllToAll by default).
verbose_progress: whether to show individual operator progress for
non-AllToAll operators as well.
"""
...
@abstractmethod
def start(self) -> None:
"""Start the progress manager."""
...
@abstractmethod
def refresh(self) -> None:
"""Refresh displayed progress."""
...
@abstractmethod
def close_with_finishing_description(self, desc: str, success: bool) -> None:
"""Close the progress manager with a finishing message.
Args:
desc: description to display
success: whether the dataset execution was successful
"""
...
@abstractmethod
def update_total_progress(self, new_rows: int, total_rows: Optional[int]) -> None:
"""Update the total progress rows.
Args:
new_rows: new rows processed by the streaming_executor
total_rows: total rows to be processed (if known)
"""
...
@abstractmethod
def update_total_resource_status(self, resource_status: str) -> None:
"""Update the total resource usage statistics.
Args:
resource_status: resource status information string.
"""
...
@abstractmethod
def update_operator_progress(
self, opstate: "OpState", resource_manager: "ResourceManager"
) -> None:
"""Update individual operator progress.
Args:
opstate: opstate of the operator.
resource_manager: the ResourceManager.
"""
...
class NoopSubProgressBar(BaseProgressBar):
"""Sub-Progress Bar for Noop (Disabled) Progress Manager"""
def __init__(self, name: str, max_name_length: int):
self._max_name_length = max_name_length
self._desc = truncate_operator_name(name, self._max_name_length)
def set_description(self, name: str) -> None:
self._desc = truncate_operator_name(name, self._max_name_length)
def get_description(self) -> str:
return self._desc
def update(self, increment: int = 0, total: Optional[int] = None) -> None:
pass
def refresh(self):
pass
def close(self):
pass
class NoopExecutionProgressManager(BaseExecutionProgressManager):
"""Noop Data Execution Progress Display Manager (Progress Display Disabled)"""
def __init__(
self,
dataset_id: str,
topology: "Topology",
show_op_progress: bool,
verbose_progress: bool,
):
for state in topology.values():
op = state.op
if not isinstance(op, SubProgressBarMixin):
continue
sub_pg_names = op.get_sub_progress_bar_names()
if sub_pg_names is not None:
for name in sub_pg_names:
pg = NoopSubProgressBar(
name=name, max_name_length=self.MAX_NAME_LENGTH
)
op.set_sub_progress_bar(name, pg)
def start(self) -> None:
pass
def refresh(self) -> None:
pass
def close_with_finishing_description(self, desc: str, success: bool) -> None:
pass
def update_total_progress(self, new_rows: int, total_rows: Optional[int]) -> None:
pass
def update_total_resource_status(self, resource_status: str) -> None:
pass
def update_operator_progress(
self, opstate: "OpState", resource_manager: "ResourceManager"
) -> None:
pass