* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#include "sim_plugin_manager.h"
#include <cstdint>
#include <cstring>
#include <dirent.h>
#include <fstream>
#include <iostream>
#include <libgen.h>
#include <limits>
#include <mutex>
#include <stack>
#include <stdexcept>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
#include "cmd_base_utils.h"
#include "sim_common_api.h"
#include "store_dump_shm_data.h"
#include "sim_log.h"
#include "sim_process_syncer.h"
using namespace HcclSim;
HcclPluginManager& HcclPluginManager::GetInstance()
{
static HcclPluginManager instance;
return instance;
}
bool HcclPluginManager::IsMatchingPlugin(const std::string& manifestPath, const std::string& targetTag)
{
if (access(manifestPath.c_str(), R_OK) != 0) {
return false;
}
try {
std::ifstream f(manifestPath);
if (!f.is_open()) {
return false;
}
nlohmann::json data = nlohmann::json::parse(f);
const std::string& nameKey = HcclPlugin::Manifest::pluginName;
if (data.contains(nameKey) && data[nameKey].is_string()) {
std::string currentPluginName = data[nameKey].get<std::string>();
if (currentPluginName == targetTag) {
if (!data.contains(HcclPlugin::Manifest::pluginEntry)) {
HCCL_VM_ERROR("'{}' missing entry field.", targetTag);
return false;
}
return true;
}
}
} catch (const nlohmann::json::parse_error& e) {
HCCL_VM_ERROR("JSON Parse Error at byte {}", e.byte);
return false;
} catch (const std::exception& e) {
HCCL_VM_ERROR("Error: {}", e.what());
return false;
}
return false;
}
bool HcclPluginManager::GetPluginFolderPath(const std::string& pluginTag, std::string& pluginFolderPath)
{
std::string rootDir = InstallPath::ResolveToInstallRoot(HcclPlugin::PLUGIN_PATH.substr(1));
std::string foundPath = "";
bool hasConflict = false;
struct ScanTask {
std::string path;
int depth;
};
std::stack<ScanTask> taskStack;
taskStack.push({rootDir, 0});
while (!taskStack.empty()) {
ScanTask current = taskStack.top();
taskStack.pop();
std::string manifestPath = current.path + HcclPlugin::MANIFEST_FILE;
if (IsMatchingPlugin(manifestPath, pluginTag)) {
char absPath[PATH_MAX];
if (realpath(current.path.c_str(), absPath)) {
std::string currentAbsPath(absPath);
if (!foundPath.empty() && foundPath != currentAbsPath) {
HCCL_VM_ERROR(
"Conflict detected: Tag '{}' exists in both: {} and {}", pluginTag, foundPath, currentAbsPath);
hasConflict = true;
break;
}
foundPath = currentAbsPath;
}
continue;
}
if (current.depth < HcclPlugin::MAX_SCAN_DEPTH) {
DIR* dir = opendir(current.path.c_str());
if (!dir) {
continue;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::string name = entry->d_name;
if (name == "." || name == "..") {
continue;
}
std::string nextPath = current.path + "/" + name;
struct stat st;
if (lstat(nextPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
taskStack.push({nextPath, current.depth + 1});
}
}
closedir(dir);
}
}
if (hasConflict) {
pluginFolderPath = "";
return false;
}
if (!foundPath.empty()) {
pluginFolderPath = foundPath;
return true;
}
return false;
}
std::vector<std::string> HcclPluginManager::GetPluginStatus() const
{
std::vector<std::string> result;
std::stringstream header;
header << std::left << std::setw(20) << "PLUGIN_TAG" << std::setw(10) << "PID" << std::setw(15) << "STATUS";
result.push_back(header.str());
std::lock_guard<std::mutex> lock(m_mutex);
for (const auto& pair : m_plugins) {
const std::string& tag = pair.first;
const auto& plugin = pair.second;
int32_t pid = plugin->GetPid();
bool running = plugin->IsRunning();
std::string statusStr = running ? "RUNNING" : "STOPPED/EXITED";
std::stringstream row;
row << std::left << std::setw(20) << tag << std::setw(10) << (pid > 0 ? std::to_string(pid) : "N/A")
<< std::setw(15) << statusStr;
result.push_back(row.str());
}
return result;
}
HcclVmResult HcclPluginManager::RegisterPlugin(const std::string& pluginTag)
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_plugins.find(pluginTag) != m_plugins.end()) {
HCCL_VM_WARN("Plugin {} already registered", pluginTag);
return HcclSim::HcclVmResult::HCCL_SIM_SUCCESS;
}
std::string folderPath;
if (GetPluginFolderPath(pluginTag, folderPath) == false) {
return HcclSim::HcclVmResult::HCCL_SIM_E_NOT_FOUND;
}
try {
auto plugin = std::make_shared<HcclPlugin>(folderPath);
m_plugins[pluginTag] = plugin;
} catch (const std::exception& e) {
HCCL_VM_ERROR("Failed to register plugin {}: {}", pluginTag, e.what());
return HcclSim::HcclVmResult::HCCL_SIM_E_INTERNAL;
}
return HcclSim::HcclVmResult::HCCL_SIM_SUCCESS;
}
HcclVmResult HcclPluginManager::SendMessageToPlugin(
const std::string& pluginTag, const std::string& action, const nlohmann::json& payload)
{
std::lock_guard<std::mutex> lock(m_mutex);
auto it = m_plugins.find(pluginTag);
if (it == m_plugins.end()) {
HCCL_VM_ERROR("Plugin {} not found", pluginTag);
return HcclVmResult::HCCL_SIM_E_NOT_FOUND;
}
HcclVmResult ret = it->second->SendMessage(PLUGIN_MESSAGE_TYPE::BROADCAST, action, payload);
if (ret != HcclVmResult::HCCL_SIM_SUCCESS) {
HCCL_VM_ERROR("Failed to send message to plugin {}: {}", pluginTag, static_cast<int>(ret));
}
return ret;
}
HcclVmResult HcclPluginManager::BroadcastToAllPlugin(const std::string& action, const nlohmann::json& payload)
{
std::lock_guard<std::mutex> lock(m_mutex);
HcclVmResult ret = HcclVmResult::HCCL_SIM_SUCCESS;
for (auto& pair : m_plugins) {
if (pair.second->IsRunning()) {
HcclVmResult singleRet = pair.second->SendMessage(PLUGIN_MESSAGE_TYPE::BROADCAST, action, payload);
if (singleRet != HcclVmResult::HCCL_SIM_SUCCESS) {
ret = HcclVmResult::HCCL_SIM_E_INTERNAL;
}
}
}
return ret;
}
std::vector<HcclSim::HcclVmResult> HcclPluginManager::StartPlugins(const std::vector<std::string>& tags)
{
std::vector<HcclSim::HcclVmResult> results;
std::lock_guard<std::mutex> lock(m_mutex);
for (const auto& tag : tags) {
auto it = m_plugins.find(tag);
if (it == m_plugins.end()) {
results.push_back(HcclSim::HcclVmResult::HCCL_SIM_E_NOT_FOUND);
continue;
}
if (it->second->IsRunning()) {
results.push_back(HcclSim::HcclVmResult::HCCL_SIM_SUCCESS);
} else {
results.push_back(it->second->Start());
}
}
return results;
}
HcclVmResult ExitRunnerPlugin()
{
sim::ProcessSyncer syncer;
syncer.signalRunnerExit();
HCCL_VM_INFO("Runner exit signal sent.");
return HcclVmResult::HCCL_SIM_SUCCESS;
}
std::vector<HcclSim::HcclVmResult> HcclPluginManager::StopPlugins(const std::vector<std::string>& tags)
{
std::vector<HcclSim::HcclVmResult> results;
std::lock_guard<std::mutex> lock(m_mutex);
for (const auto& tag : tags) {
if (tag == "runner") {
auto exitRet = ExitRunnerPlugin();
auto it = m_plugins.find(tag);
if (it == m_plugins.end()) {
results.push_back(exitRet);
continue;
}
auto stopRet = it->second->Stop();
m_plugins.erase(it);
results.push_back(exitRet != HcclVmResult::HCCL_SIM_SUCCESS ? exitRet : stopRet);
continue;
}
auto it = m_plugins.find(tag);
if (it == m_plugins.end()) {
results.push_back(HcclSim::HcclVmResult::HCCL_SIM_E_NOT_FOUND);
continue;
}
HcclSim::HcclVmResult res = it->second->Stop();
results.push_back(res);
m_plugins.erase(it);
}
return results;
}
HcclSim::HcclVmResult HcclPluginManager::StopAllPlugins()
{
std::vector<std::string> allTags;
{
std::lock_guard<std::mutex> lock(m_mutex);
for (const auto& pair : m_plugins) {
allTags.push_back(pair.first);
}
}
if (allTags.empty()) {
return HcclSim::HcclVmResult::HCCL_SIM_SUCCESS;
}
StopPlugins(allTags);
return HcclSim::HcclVmResult::HCCL_SIM_SUCCESS;
}
HcclPluginManager::HcclPluginManager()
{
HCCL_VM_INFO("HcclPluginManager");
m_monitorThreadStop = false;
m_monitorThread = std::thread([this]() {
this->MonitorThread();
});
}
HcclPluginManager::~HcclPluginManager()
{
m_monitorThreadStop = true;
StopAllPlugins();
if (m_monitorThread.joinable()) {
m_monitorThread.join();
}
}
void HcclPluginManager::MonitorThread()
{
int status;
while (!m_monitorThreadStop) {
pid_t terminatedPid = waitpid(-1, &status, WNOHANG);
if (terminatedPid > 0) {
std::lock_guard<std::mutex> lock(m_mutex);
for (auto it : m_plugins) {
if (it.second->GetPid() == terminatedPid) {
if (WIFEXITED(status)) {
HCCL_VM_INFO(
"Plugin [{}] (PID: {}) exit normally. Code {}", it.second->GetTag(), terminatedPid,
WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
HCCL_VM_ERROR(
"Plugin [{}] (PID: {}) exit with failure. Code {} ({})", it.second->GetTag(), terminatedPid,
WTERMSIG(status), strsignal(WTERMSIG(status)));
}
m_plugins.erase(it.first);
break;
}
}
} else if (terminatedPid == 0) {
std::this_thread::sleep_for(std::chrono::seconds(1));
} else if (terminatedPid == -1) {
if (errno == ECHILD) {
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
}
}
}