* Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
*
* ubs-optimizer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* 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 FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "qemu_isolate_tuner.h"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <string>
#include "cmd_executor.h"
#include "log/ebpf_logger_macros.h"
#include "utils.h"
const std::string_view DATA_PATH = "/var/ubs-opt/data/";
const std::string_view DATA_FILE_NAME = "data.json";
constexpr const double TUNE_TRIGGER_PERCENT = 0.9;
constexpr const int NOT_FOUND = -1;
int maxQemuMvcount = -1;
int QemuIsolTuner::interval;
std::string QemuIsolTuner::name() const
{
return "QEMU Process Isolation";
}
std::string QemuIsolTuner::category() const
{
return "CPU BOUND";
}
std::string QemuIsolTuner::principle() const
{
return "The QEMU process frequently switches CPUs, resulting in significant virtualization "
"overhead.";
}
std::string QemuIsolTuner::advice() const
{
return "Enable QEMU process isolation. physical machine and virtual machine topology synchronization in "
"pass-through scenarios.";
}
bool QemuIsolTuner::check()
{
EBPF_LOG_INFO("Checking QemuiSolate tunner.");
ResultCode res = checkApply();
if (res == ResultCode::SUCCESS) {
EBPF_LOG_INFO("QemuIsolate configuration has been set up.");
return false;
} else if (res == ResultCode::ERROR) {
isLastCheckSuccess = false;
return true;
}
EBPF_LOG_INFO("QemuIsolate configuration has not been set up. ");
isLastCheckSuccess = true;
findLastInfer();
if (maxQemuMvcount < static_cast<int>(interval * TUNE_TRIGGER_PERCENT)) {
EBPF_LOG_INFO("QemuIsolate configuration is not needed.");
return false;
}
EBPF_LOG_INFO("QemuIsolate configuration is needed.");
return true;
}
void QemuIsolTuner::apply()
{
std::cout << "1. On the physical machine, query the process ID of QEMU." << std::endl
<< "2. Set the CPU binding for QEMU using \"taskset -cp CPU_ID QEMU_ID\","
"where CPU_ID should be set to a CPU not allocated to the virtual machine. "
<< std::endl;
}
QemuIsolTuner::ResultCode QemuIsolTuner::checkApply()
{
const rapidjson::Value vmname_value = utils::getConfigValue("vm_name");
if (vmname_value.IsNull()) {
isLastCheckSuccess = false;
EBPF_LOG_ERROR("Optimizer parse config failed, can not find vm_name.");
return ResultCode::ERROR;
}
std::string vm_name = vmname_value.GetString();
static const char *cmd = "vmtop -b -n 1";
CmdExecutor executor;
auto result = executor.runCommand(cmd);
if (!result.first) {
isLastCheckSuccess = false;
return ResultCode::ERROR;
}
std::istringstream iss(result.second);
std::string line;
int pid_pos = NOT_FOUND;
unsigned int pid = 0;
while (std::getline(iss, line)) {
if (line.find("VM/task-name") != std::string::npos) {
std::vector<std::string> tokens = splitBySpace(line);
pid_pos = findPIndex(tokens);
if (pid_pos == -1) {
EBPF_LOG_ERROR("QemuIsolTuner::checkApply run vmtop -b -n 1 failed.");
isLastCheckSuccess = false;
return ResultCode::ERROR;
}
}
if (line.find(vm_name) != std::string::npos && pid_pos != NOT_FOUND) {
std::vector<std::string> tokens = splitBySpace(line);
pid = static_cast<unsigned int>(std::stoi(tokens[pid_pos]));
break;
}
}
std::string tasksetCmd = "taskset -p " + std::to_string(pid);
result = executor.runCommand("taskset -p " + std::to_string(pid));
if (!result.first) {
isLastCheckSuccess = false;
return ResultCode::ERROR;
}
auto isApply = isAllF(result.second);
if (isApply == ResultCode::SUCCESS) {
return ResultCode::FALSE;
} else if (isApply == ResultCode::ERROR) {
isLastCheckSuccess = false;
return ResultCode::ERROR;
}
return ResultCode::SUCCESS;
}
int QemuIsolTuner::findPIndex(const std::vector<std::string> &tokens)
{
for (size_t i = 0; i < tokens.size(); ++i) {
if (tokens[i] == "PID") {
return static_cast<int>(i);
}
}
return -1;
}
QemuIsolTuner::ResultCode QemuIsolTuner::isAllF(const std::string &line)
{
size_t colon_pos = line.find(":");
if (colon_pos == std::string::npos) {
EBPF_LOG_ERROR("QemuIsolTuner::isAllF run taskset command failed.");
return ResultCode::ERROR;
}
std::string after_colon = line.substr(colon_pos + 1);
after_colon.erase(std::remove_if(after_colon.begin(), after_colon.end(), ::isspace), after_colon.end());
if (std::all_of(after_colon.begin(), after_colon.end(), [](char c) { return c == 'f'; })) {
return ResultCode::SUCCESS;
} else {
return ResultCode::FALSE;
}
}
std::vector<std::string> QemuIsolTuner::splitBySpace(const std::string &line)
{
std::istringstream iss(line);
std::vector<std::string> tokens;
std::string token;
while (iss >> token) {
tokens.push_back(token);
}
return tokens;
}
std::ifstream QemuIsolTuner::openDataFile(std::string_view filename)
{
char *dataPath = realpath(std::string().append(DATA_PATH).append(filename).c_str(), nullptr);
const std::string path(dataPath);
const std::string renamedPath = path + ".bak";
if (std::filesystem::exists(renamedPath)) {
std::filesystem::remove(renamedPath);
}
std::filesystem::copy(path, renamedPath);
std::ifstream file(renamedPath);
free(dataPath);
return file;
}
void QemuIsolTuner::closeDataFile(std::string_view filename, std::ifstream file)
{
char *dataPath = realpath(std::string().append(DATA_PATH).append(filename).append(".bak").c_str(), nullptr);
const std::string renamedPath(dataPath);
file.close();
std::filesystem::remove(renamedPath);
free(dataPath);
}
void QemuIsolTuner::parseHostData(const std::string &rawJson)
{
rapidjson::Document doc;
rapidjson::ParseResult ok = doc.Parse(rawJson.c_str());
if (!ok) {
throw ::std::runtime_error("Not json format.");
}
if (!doc.HasMember("guest_name")) {
throw ::std::runtime_error("Does not have 'guest_name'.");
}
if (doc.HasMember("data_table") && doc["data_table"].IsObject()) {
const rapidjson::Value &host_data = doc["data_table"];
if (host_data.HasMember("qemu_migration_count") && host_data["qemu_migration_count"].IsInt()) {
int migration_count = host_data["qemu_migration_count"].GetInt();
maxQemuMvcount = std::max(maxQemuMvcount, migration_count);
}
}
if (!doc.HasMember("interval") || !doc["interval"].IsInt()) {
throw ::std::runtime_error("Does not have 'interval' or 'interval' is not int.");
}
if (doc["interval"].GetInt() <= 0) {
throw ::std::runtime_error("interval must be a positive integer, current value: " +
std::to_string(doc["interval"].GetInt()));
}
interval = doc["interval"].GetInt();
}
void QemuIsolTuner::findLastInfer()
{
EBPF_LOG_INFO("Checking Qemu isolate tunner.");
std::ifstream file;
try {
file = openDataFile(DATA_FILE_NAME);
if (!file.is_open()) {
isLastCheckSuccess = false;
EBPF_LOG_ERROR("Failed to found data file: " + std::string(DATA_PATH) + std::string(DATA_FILE_NAME));
return;
}
} catch (const std::exception &e) {
isLastCheckSuccess = false;
EBPF_LOG_WARN("Unable to open data file: " + std::string(e.what()));
return;
}
std::string rawJson;
time_t lastTimestamp = 0;
while (std::getline(file, rawJson)) {
try {
parseHostData(rawJson);
} catch (const std::exception &e) {
continue;
}
}
closeDataFile(DATA_FILE_NAME, std::move(file));
}