Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_multicore / shmem / _debug.py: 86%
51 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 05:03 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 05:03 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Local Runtime snapshots and best-effort SHMEM Allocation diagnostics."""
17from __future__ import annotations
19__all__ = ["debug_state"]
21import os
22import traceback
23from collections.abc import Mapping
24from typing import Any
26from ._lifecycle import _reference_count
27from ._runtime import _load_native
30_HEX_ADDRESS_FIELDS = frozenset({"allocation_base"})
31_LOG_LEVEL_ENV = "SHMEM_LOG_LEVEL"
32_INTERNAL_FRAME_PREFIX = os.path.dirname(os.path.abspath(__file__)) + os.sep
35def _format_debug_value(field: str, value: object) -> object:
36 """Render known address fields in hex; everything else keeps its natural repr."""
37 if field in _HEX_ADDRESS_FIELDS:
38 return hex(value)
39 return value
42def _render_debug_fields(fields: Mapping[str, object], indent: int = 0) -> list[str]:
43 """Render diagnostic fields one per line, nesting mappings and mapping lists."""
44 pad = " " * indent
45 lines: list[str] = []
46 for key, value in fields.items():
47 if isinstance(value, Mapping):
48 lines.append(f"{pad}{key}:")
49 lines.extend(_render_debug_fields(value, indent + 1))
50 elif isinstance(value, list) and value and all(isinstance(item, Mapping) for item in value):
51 lines.append(f"{pad}{key}:")
52 for item in value:
53 rendered = ", ".join(
54 f"{field}={_format_debug_value(field, item[field])}" for field in item
55 )
56 lines.append(f"{pad} - {rendered}")
57 else:
58 lines.append(f"{pad}{key}: {_format_debug_value(key, value)}")
59 return lines
62def _log_allocation_site(tensor: Any) -> None:
63 """Best-effort DEBUG line identifying the direct caller of :func:`empty`."""
64 if os.environ.get(_LOG_LEVEL_ENV) != "DEBUG":
65 return
66 try:
67 frames = traceback.extract_stack()
68 caller = next(
69 (
70 frame
71 for frame in reversed(frames)
72 if not os.path.abspath(frame.filename).startswith(_INTERNAL_FRAME_PREFIX)
73 ),
74 None,
75 )
76 callsite = "unknown" if caller is None else f"{caller.filename}:{caller.lineno} in {caller.name}"
77 message = (
78 f"allocation_base={hex(tensor.data_ptr())} "
79 f"bytes={tensor.numel() * tensor.element_size()} shape={tuple(tensor.shape)} dtype={tensor.dtype} "
80 f"callsite={callsite}"
81 )
82 _load_native()._log_debug(message) # pylint: disable=protected-access
83 except Exception: # pylint: disable=broad-exception-caught
84 # Diagnostics must never turn a successful collective Allocation into a leaked Allocation.
85 return
88class _DebugState(dict):
89 """Diagnostic snapshot dict rendered one field per line for human reading."""
91 def __repr__(self) -> str:
92 """Render diagnostic fields one per line for interactive inspection."""
93 return "\n".join(_render_debug_fields(self))
96def debug_state() -> dict[str, object]:
97 """Return a local, read-only snapshot of current Runtime facts and its latest failure.
99 This query does not require an acquired Runtime reference. ``reference_count`` is the number of successful
100 process-local ``shmem.acquire()`` calls not yet paired with ``shmem.release()``; ranks may report different values.
101 While the Runtime is Ready, ``config`` carries the effective Runtime configuration (``heap_size_bytes``,
102 ``timeout_seconds``, ``data_engine``, ``bootstrap_endpoint_base``). Heap usage is projected from requested bytes:
103 ``allocated_bytes`` sums the requested sizes of all active Allocations, ``remaining_bytes`` is
104 ``heap_size_bytes - allocated_bytes``, and ``active_allocations`` lists every active Allocation as
105 ``{"allocation_id", "allocation_base", "allocation_bytes"}`` ordered by identity. Compare a Tensor's
106 ``data_ptr()`` against ``allocation_base`` ranges to find its Allocation; the REPL renders addresses in hex while
107 stored values remain integers. CANN rounds blocks to 16-byte multiples, so true device occupancy can be higher.
108 ``leaked_allocations`` lists Allocations whose last Tensor Storage reference was dropped without
109 ``shmem.free()``, in drop order, using the same fields as ``active_allocations``.
111 After the final clean ``shmem.release()``, the state is ``Uninitialized`` and lifecycle-specific Root, device,
112 config, Allocation, maximum-active-allocation, leaked-allocation, and latest-failure fields are ``None`` until
113 the next lifecycle starts.
114 """
115 reference_count = _reference_count()
116 native_state = dict(_load_native()._debug_state()) # pylint: disable=protected-access
117 state = _DebugState()
118 state["state"] = native_state.pop("state")
119 state["reference_count"] = reference_count
120 state.update(native_state)
121 return state