from affinity_sched.affinity_domain import AffinityDomainManager
from affinity_sched.task import TaskManager
from affinity_sched.backup import AffinityBackup
from affinity_sched.config import AffinityConfig
from affinity_sched.scheduler import Scheduler
from affinity_sched.strategy.hierarchical_balance import HierarchicalBalanceScheduler
import affinity_sched.utils as utils
class AffinityEngine:
"""亲和调度引擎"""
def __init__(self) -> None:
self._scheduler: Scheduler | None = None
self._init_engine()
def _init_engine(self) -> None:
self.config = AffinityConfig()
self.domain = AffinityDomainManager(config=self.config)
self.task = TaskManager(domain=self.domain)
self.backup = AffinityBackup(task=self.task)
def run(self, dry_run: bool = False) -> None:
"""
运行亲和调度
Args:
dry_run: True表示试运行,仅输出亲和方案,不做亲和方案执行;False表示运行亲和调度全流程
"""
print("\n--------------------------- Affinity Schedule Begin ---------------------------")
try:
self._check_device()
print("\nStarting build affinity domain...")
self.domain.build_affinity_domain()
print("The affinity domain is as follows:")
self._print_affinity_domain()
print("\nStarting init npu...")
self.task.init_npu()
print("\nStarting scan backgound task...")
self.task.scan_background_tasks()
print("\nThe affinity info before schedule is as follows:")
self.print_affinity()
print("\nStarting plan affinity...")
self._plan()
print("The affinity plan is as follows:")
self._print_affinity_plan()
if not dry_run:
print("\nStarting execute affinity...")
self._execute()
print("\nThe affinity info after schedule is as follows:")
self.print_affinity()
print("\nAffinity schedule SUCCESS!")
except Exception as e:
print(f"\nAffinity schedule FAILED! Error: {e}")
print("---------------------------- Affinity Schedule End ----------------------------")
def _plan(self) -> None:
self.task.update_high_prio_thread_bind_npu()
self._scheduler = HierarchicalBalanceScheduler(self.config, self.domain, self.task)
if not self._scheduler.schedule():
raise RuntimeError("Plan affinity failed!")
def _execute(self) -> None:
self.backup_affinity()
self._stop_irq_balance()
self._bind_cpus()
self._bind_memory()
def _print_affinity_domain(self) -> None:
print("------------------------------- Affinity Domain -------------------------------")
self.domain.print_all()
def _print_affinity_plan(self) -> None:
print("-------------------------------- Affinity Plan --------------------------------")
self.task.print_all()
def _check_device(self) -> None:
device = utils.get_ascend_device_type()
if device == utils.AscendDeviceType.UNKNOWN:
raise RuntimeError(f"Can not detect ascend device type")
if device not in (utils.AscendDeviceType.A3, utils.AscendDeviceType.A5):
raise RuntimeError(f"Unsupported ascend device type [{device}], support A3/A5 only")
def _stop_irq_balance(self) -> None:
print("\nStopping irqbalance service...")
_, _, return_code = utils.execute_command(["systemctl", "is-active", "--quiet", "irqbalance"])
if return_code == 0:
utils.execute_command(["systemctl", "stop", "irqbalance"])
print("the irqbalance service has been stopped.")
def _bind_cpus(self) -> None:
print("\nStarting bind cpus...")
for _, group in self.task.groups.items():
for task in group.all_tasks:
task.bind_cpu()
self._bind_background_tasks()
def _bind_background_tasks(self) -> None:
normal_cpus = self.task.background_tasks_cpus
if not normal_cpus:
print("[BackgrondTask] No target CPUs found, skipping background tasks binding.")
return
failed = []
bound = 0
for pid, name in self.task.background_processes.items():
try:
utils.bind_process_to_cpus(pid, normal_cpus)
bound += 1
except Exception as e:
failed.append((pid, name, str(e)))
if failed:
print(f"[BackgroundTask] Failed to bind {len(failed)} processes.")
for pid, name, err in failed:
print(f" - process[{pid}]({name}): {err}")
total = len(self.task.background_processes)
print(
f"[BackgroundTask] Bind {bound}/{total} processes to CPUs {utils.compress_continuous(normal_cpus)}"
f" (failed: {len(failed)})"
)
def _bind_memory(self) -> None:
print("\nStarting bind memory...")
for _, group in self.task.groups.items():
for _, process in group.process_tasks.items():
if not process.numa:
print(f"can not get target numa of process [{process.task_id}]")
continue
tgt_numa = process.numa[0]
src_numa = self.domain.get_all_numas_id()
if not src_numa:
print(f"can not get source numa of process [{process.task_id}]")
continue
print(
f"migrating pages of process [{process.task_id}] from source numa {src_numa} to target numa [{tgt_numa}]"
)
try:
utils.migrate_process_pages(pid=process.task_id, src_numa=src_numa, tgt_numa=tgt_numa)
except Exception as e:
print(f"Failed to migrate pages for process [{process.task_id}]: {str(e)}")
def print_affinity(self) -> None:
"""打印亲和任务中进程/线程当前实际的亲和信息"""
print("--------------------------- Current Affinity Status ---------------------------")
self.task.print_actual_affinity()
def backup_affinity(self) -> None:
"""备份亲和任务中进程/线程当前亲和信息"""
print("\nStarting backup current affinity...")
self.backup.backup_affinity()
def restore_affinity(self) -> None:
"""恢复亲和任务中进程/线程原始亲和信息"""
print("\n--------------------------- Affinity Resotre Begin ----------------------------")
print("Starting restore affinity...")
self.backup.restore_affinity()
print("\nThe affinity after restore is as follows:")
self.print_affinity()
print(f"\nAffinity restore SUCCESS!")
print("---------------------------- Affinity Resotre End -----------------------------")
def reset(self) -> None:
self._init_engine()
affinity_engine = AffinityEngine()