Skip to content

guidellm.collectors.process

Run a benchmark's collectors in a separate process.

The main process already drives the scheduler and compiles results, so all collectors share one child process with its own event loop. The main process only tells it when each benchmark starts and stops, and asks for the results once the benchmark's measurement window is known.

CollectorProcess

Own the child process that runs a benchmark's collectors.

Example: ::

    collectors = CollectorProcess(args)
    await collectors.start()
    try:
        await collectors.benchmark_started()
        ...  # run the benchmark
        await collectors.benchmark_stopped()
        results = await collectors.results(start_time, end_time)
    finally:
        await collectors.shutdown()
Source code in src/guidellm/collectors/process.py
class CollectorProcess:
    """
    Own the child process that runs a benchmark's collectors.

    Example:
        ::

            collectors = CollectorProcess(args)
            await collectors.start()
            try:
                await collectors.benchmark_started()
                ...  # run the benchmark
                await collectors.benchmark_stopped()
                results = await collectors.results(start_time, end_time)
            finally:
                await collectors.shutdown()
    """

    def __init__(self, args: list[CollectorArgs]):
        """
        :param args: Arguments for each collector, in the order results are returned
        """
        self.args = args
        self._process: BaseProcess | None = None
        self._connection: Connection | None = None

    async def start(self) -> None:
        """
        Start the child process and create the collectors in it.

        :raises RuntimeError: If a collector cannot be created
        """
        context = get_context(settings.mp_context_type)
        parent_connection, child_connection = context.Pipe()
        self._process = context.Process(
            target=_run_collectors,
            args=(child_connection, [arg.model_dump() for arg in self.args]),
            name="guidellm-collectors",
            daemon=True,
        )
        self._process.start()
        child_connection.close()
        self._connection = parent_connection
        await self._request("ready")

    async def benchmark_started(self) -> None:
        """Start every collector for a new benchmark."""
        await self._request("start")

    async def benchmark_stopped(self) -> None:
        """Stop every collector once the benchmark's requests have ended."""
        await self._request("stop")

    async def results(self, start_time: float, end_time: float) -> list[dict[str, Any]]:
        """
        Return each collector's result for the latest benchmark.

        :param start_time: Start of the measurement window in seconds since epoch
        :param end_time: End of the measurement window in seconds since epoch
        :return: One result per collector, in configuration order
        """
        return await self._request("result", start_time, end_time)

    async def shutdown(self) -> None:
        """Close the collectors and wait for the child process to exit."""
        if self._process is None or self._connection is None:
            return
        try:
            await self._request("close")
        except (EOFError, OSError, RuntimeError) as err:
            logger.warning("Collector process did not close cleanly: {}", err)
        self._connection.close()
        await asyncio.to_thread(self._process.join, _SHUTDOWN_TIMEOUT)
        if self._process.is_alive():
            self._process.terminate()
        self._process = None
        self._connection = None

    async def _request(self, command: str, *params: Any) -> Any:
        if self._connection is None:
            raise RuntimeError("Collector process has not been started")
        if command != "ready":
            self._connection.send((command, *params))
        status, payload = await asyncio.to_thread(self._connection.recv)
        if status != "ok":
            raise RuntimeError(f"Collector process failed on {command!r}: {payload}")

        return payload

__init__(args)

Parameters:

Name Type Description Default
args list[CollectorArgs]

Arguments for each collector, in the order results are returned

required
Source code in src/guidellm/collectors/process.py
def __init__(self, args: list[CollectorArgs]):
    """
    :param args: Arguments for each collector, in the order results are returned
    """
    self.args = args
    self._process: BaseProcess | None = None
    self._connection: Connection | None = None

benchmark_started() async

Start every collector for a new benchmark.

Source code in src/guidellm/collectors/process.py
async def benchmark_started(self) -> None:
    """Start every collector for a new benchmark."""
    await self._request("start")

benchmark_stopped() async

Stop every collector once the benchmark's requests have ended.

Source code in src/guidellm/collectors/process.py
async def benchmark_stopped(self) -> None:
    """Stop every collector once the benchmark's requests have ended."""
    await self._request("stop")

results(start_time, end_time) async

Return each collector's result for the latest benchmark.

Parameters:

Name Type Description Default
start_time float

Start of the measurement window in seconds since epoch

required
end_time float

End of the measurement window in seconds since epoch

required

Returns:

Type Description
list[dict[str, Any]]

One result per collector, in configuration order

Source code in src/guidellm/collectors/process.py
async def results(self, start_time: float, end_time: float) -> list[dict[str, Any]]:
    """
    Return each collector's result for the latest benchmark.

    :param start_time: Start of the measurement window in seconds since epoch
    :param end_time: End of the measurement window in seconds since epoch
    :return: One result per collector, in configuration order
    """
    return await self._request("result", start_time, end_time)

shutdown() async

Close the collectors and wait for the child process to exit.

Source code in src/guidellm/collectors/process.py
async def shutdown(self) -> None:
    """Close the collectors and wait for the child process to exit."""
    if self._process is None or self._connection is None:
        return
    try:
        await self._request("close")
    except (EOFError, OSError, RuntimeError) as err:
        logger.warning("Collector process did not close cleanly: {}", err)
    self._connection.close()
    await asyncio.to_thread(self._process.join, _SHUTDOWN_TIMEOUT)
    if self._process.is_alive():
        self._process.terminate()
    self._process = None
    self._connection = None

start() async

Start the child process and create the collectors in it.

Raises:

Type Description
RuntimeError

If a collector cannot be created

Source code in src/guidellm/collectors/process.py
async def start(self) -> None:
    """
    Start the child process and create the collectors in it.

    :raises RuntimeError: If a collector cannot be created
    """
    context = get_context(settings.mp_context_type)
    parent_connection, child_connection = context.Pipe()
    self._process = context.Process(
        target=_run_collectors,
        args=(child_connection, [arg.model_dump() for arg in self.args]),
        name="guidellm-collectors",
        daemon=True,
    )
    self._process.start()
    child_connection.close()
    self._connection = parent_connection
    await self._request("ready")