feat: richer hardware discovery — CPU cache/voltage, memory type/bandwidth/part-no, GPU discovery
- CPU: max speed, bus MHz, L1/L2/L3 cache (from sysfs), voltage, socket type; /proc/cpuinfo fallback for non-root - Memory sticks: DDR type, form factor, part number, rank, data width, theoretical bandwidth - GPU: new part type discovered via lspci + /sys/class/drm + nvidia-smi; shows VRAM and display outputs - discover-only tree updated to show all new fields Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
bf7a7937e0
commit
69113c1ea7
8 changed files with 654 additions and 13 deletions
Binary file not shown.
|
|
@ -291,7 +291,8 @@ std::vector<DiscoveredPart> Discovery::discover_all() {
|
|||
std::vector<DiscoveredPart> all;
|
||||
for (auto& v : {discover_memory_sticks(), discover_memory_slots(),
|
||||
discover_cpus(), discover_cpu_slots(),
|
||||
discover_disks(), discover_nics()}) {
|
||||
discover_disks(), discover_nics(),
|
||||
discover_gpus()}) {
|
||||
all.insert(all.end(), v.begin(), v.end());
|
||||
}
|
||||
return all;
|
||||
|
|
@ -304,6 +305,7 @@ std::vector<DiscoveredPart> Discovery::discover(const std::string& type_name) {
|
|||
if (type_name == PTYPE_CPU_SLOT) return discover_cpu_slots();
|
||||
if (type_name == PTYPE_DISK) return discover_disks();
|
||||
if (type_name == PTYPE_NIC) return discover_nics();
|
||||
if (type_name == PTYPE_GPU) return discover_gpus();
|
||||
std::cerr << "discover: unknown type '" << type_name << "'\n";
|
||||
return {};
|
||||
}
|
||||
|
|
@ -423,6 +425,39 @@ std::vector<DiscoveredPart> Discovery::discover_memory_sticks() {
|
|||
if (b.count("Locator") && !b["Locator"].empty())
|
||||
p.kv[K_LOCATOR] = b["Locator"];
|
||||
|
||||
// Extended memory fields
|
||||
if (b.count("Type") && b["Type"] != "Unknown" && b["Type"] != "Not Specified")
|
||||
p.kv[K_MEM_TYPE] = b["Type"];
|
||||
|
||||
if (b.count("Form Factor") && b["Form Factor"] != "Unknown" && b["Form Factor"] != "Not Specified")
|
||||
p.kv[K_FORM_FACTOR] = b["Form Factor"];
|
||||
|
||||
if (b.count("Part Number")) {
|
||||
std::string pn = trim(b["Part Number"]);
|
||||
if (!pn.empty() && pn != "Unknown" && pn != "Not Specified")
|
||||
p.kv[K_PART_NUMBER] = pn;
|
||||
}
|
||||
|
||||
if (b.count("Rank")) {
|
||||
uint32_t rank = static_cast<uint32_t>(leading_uint(b["Rank"]));
|
||||
if (rank > 0) p.kv[K_RANK] = std::to_string(rank);
|
||||
}
|
||||
|
||||
uint32_t data_width = 0;
|
||||
if (b.count("Data Width")) {
|
||||
data_width = static_cast<uint32_t>(leading_uint(b["Data Width"]));
|
||||
if (data_width > 0) p.kv[K_DATA_WIDTH] = std::to_string(data_width);
|
||||
}
|
||||
|
||||
// Compute theoretical bandwidth: speed_mhz × 2 × (data_width / 8) MB/s
|
||||
if (data_width > 0 && p.kv.count(K_SPEED_MHZ)) {
|
||||
uint64_t spd = std::stoull(p.kv[K_SPEED_MHZ]);
|
||||
if (spd > 0) {
|
||||
uint64_t bw = spd * 2ULL * (data_width / 8);
|
||||
p.kv[K_BANDWIDTH_MBPS] = std::to_string(bw);
|
||||
}
|
||||
}
|
||||
|
||||
result.push_back(std::move(p));
|
||||
}
|
||||
#endif
|
||||
|
|
@ -604,8 +639,193 @@ std::vector<DiscoveredPart> Discovery::discover_cpus() {
|
|||
if (b.count("Socket Designation") && b["Socket Designation"] != "Not Specified")
|
||||
p.kv[K_SOCKET] = b["Socket Designation"];
|
||||
|
||||
// Extended CPU fields from dmidecode type 4
|
||||
if (b.count("Max Speed")) {
|
||||
double max_mhz = static_cast<double>(leading_uint(b["Max Speed"]));
|
||||
if (max_mhz > 0) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%.3f", max_mhz / 1000.0);
|
||||
p.kv[K_MAX_SPEED_GHZ] = buf;
|
||||
}
|
||||
}
|
||||
if (b.count("External Clock")) {
|
||||
uint64_t bus_mhz = leading_uint(b["External Clock"]);
|
||||
if (bus_mhz > 0) p.kv[K_BUS_MHZ] = std::to_string(bus_mhz);
|
||||
}
|
||||
if (b.count("Voltage")) {
|
||||
std::string vs = b["Voltage"];
|
||||
// trim " V" suffix
|
||||
while (!vs.empty() && (vs.back() == 'V' || vs.back() == ' ')) vs.pop_back();
|
||||
if (!vs.empty()) {
|
||||
try {
|
||||
double v = std::stod(vs);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%.1f", v);
|
||||
p.kv[K_VOLTAGE_V] = buf;
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
if (b.count("Upgrade") && b["Upgrade"] != "Unknown" && b["Upgrade"] != "Other") {
|
||||
p.kv[K_SOCKET_TYPE] = b["Upgrade"];
|
||||
}
|
||||
|
||||
result.push_back(std::move(p));
|
||||
}
|
||||
|
||||
// Enrich CPUs with L1/L2/L3 cache sizes from sysfs
|
||||
// Build a map: physical_package_id → first cpu index
|
||||
{
|
||||
std::map<int, int> pkg_to_first_cpu;
|
||||
for (int ci = 0; ; ++ci) {
|
||||
std::string pkg_path = "/sys/devices/system/cpu/cpu"
|
||||
+ std::to_string(ci) + "/topology/physical_package_id";
|
||||
FILE* pf = fopen(pkg_path.c_str(), "r");
|
||||
if (!pf) break;
|
||||
int pkg = -1;
|
||||
fscanf(pf, "%d", &pkg);
|
||||
fclose(pf);
|
||||
if (pkg >= 0 && pkg_to_first_cpu.find(pkg) == pkg_to_first_cpu.end())
|
||||
pkg_to_first_cpu[pkg] = ci;
|
||||
}
|
||||
|
||||
// For each CPU entry in result (in order), pick socket 0, 1, 2 ...
|
||||
int socket_idx = 0;
|
||||
for (auto& p : result) {
|
||||
if (p.type_name != PTYPE_CPU) { ++socket_idx; continue; }
|
||||
auto it = pkg_to_first_cpu.find(socket_idx);
|
||||
int cpu_idx = (it != pkg_to_first_cpu.end()) ? it->second : socket_idx;
|
||||
|
||||
// Walk cache indices for this cpu
|
||||
uint32_t l1_kb = 0, l2_kb = 0, l3_kb = 0;
|
||||
for (int idx = 0; idx < 8; ++idx) {
|
||||
std::string base = "/sys/devices/system/cpu/cpu"
|
||||
+ std::to_string(cpu_idx) + "/cache/index"
|
||||
+ std::to_string(idx);
|
||||
// Read level
|
||||
int level = 0;
|
||||
{
|
||||
FILE* lf = fopen((base + "/level").c_str(), "r");
|
||||
if (!lf) break;
|
||||
fscanf(lf, "%d", &level);
|
||||
fclose(lf);
|
||||
}
|
||||
// Read type
|
||||
std::string cache_type;
|
||||
{
|
||||
FILE* tf = fopen((base + "/type").c_str(), "r");
|
||||
if (tf) {
|
||||
char tbuf[32] = {};
|
||||
fscanf(tf, "%31s", tbuf);
|
||||
fclose(tf);
|
||||
cache_type = tbuf;
|
||||
}
|
||||
}
|
||||
// Read size
|
||||
std::string size_str;
|
||||
{
|
||||
FILE* sf = fopen((base + "/size").c_str(), "r");
|
||||
if (sf) {
|
||||
char sbuf[32] = {};
|
||||
fscanf(sf, "%31s", sbuf);
|
||||
fclose(sf);
|
||||
size_str = sbuf;
|
||||
}
|
||||
}
|
||||
if (size_str.empty()) continue;
|
||||
// Parse size: "32K", "256K", "9216K", "6144K"
|
||||
uint32_t kb = 0;
|
||||
{
|
||||
size_t ni = 0;
|
||||
while (ni < size_str.size() && std::isdigit((unsigned char)size_str[ni])) ++ni;
|
||||
if (ni > 0) {
|
||||
kb = static_cast<uint32_t>(std::stoul(size_str.substr(0, ni)));
|
||||
std::string unit = size_str.substr(ni);
|
||||
if (!unit.empty() && (unit[0] == 'M' || unit[0] == 'm')) kb *= 1024;
|
||||
}
|
||||
}
|
||||
if (kb == 0) continue;
|
||||
if (level == 1 && cache_type == "Data") l1_kb += kb;
|
||||
else if (level == 1 && cache_type == "Instruction") {} // skip icache
|
||||
else if (level == 1 && cache_type == "Unified") l1_kb += kb;
|
||||
else if (level == 2) l2_kb += kb;
|
||||
else if (level == 3) l3_kb += kb;
|
||||
}
|
||||
|
||||
if (l1_kb > 0) p.kv[K_CACHE_L1_KB] = std::to_string(l1_kb);
|
||||
if (l2_kb > 0) p.kv[K_CACHE_L2_KB] = std::to_string(l2_kb);
|
||||
if (l3_kb > 0) p.kv[K_CACHE_L3_KB] = std::to_string(l3_kb);
|
||||
++socket_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// /proc/cpuinfo fallback if dmidecode gave nothing
|
||||
if (result.empty()) {
|
||||
std::string cpuinfo = run_cmd("cat /proc/cpuinfo 2>/dev/null");
|
||||
if (!cpuinfo.empty()) {
|
||||
// Group by physical id
|
||||
std::map<int, std::map<std::string,std::string>> by_phys;
|
||||
std::map<std::string,std::string> cur_block;
|
||||
int cur_phys = -1;
|
||||
std::istringstream cs(cpuinfo);
|
||||
std::string cl;
|
||||
auto flush_cpuinfo = [&]() {
|
||||
if (cur_phys < 0) return;
|
||||
auto& target = by_phys[cur_phys];
|
||||
for (auto& [k, v] : cur_block) {
|
||||
if (target.find(k) == target.end())
|
||||
target[k] = v;
|
||||
}
|
||||
};
|
||||
while (std::getline(cs, cl)) {
|
||||
if (cl.empty()) continue;
|
||||
size_t colon = cl.find(':');
|
||||
if (colon == std::string::npos) continue;
|
||||
std::string key = trim(cl.substr(0, colon));
|
||||
std::string val = trim(cl.substr(colon + 1));
|
||||
if (key == "physical id") {
|
||||
int phys = std::stoi(val);
|
||||
if (phys != cur_phys) {
|
||||
flush_cpuinfo();
|
||||
cur_phys = phys;
|
||||
cur_block.clear();
|
||||
}
|
||||
} else {
|
||||
cur_block[key] = val;
|
||||
}
|
||||
}
|
||||
flush_cpuinfo();
|
||||
for (auto& [phys_id, fields] : by_phys) {
|
||||
DiscoveredPart p;
|
||||
p.type_name = PTYPE_CPU;
|
||||
if (fields.count("model name")) p.kv[K_NAME] = fields["model name"];
|
||||
if (fields.count("cpu MHz")) {
|
||||
try {
|
||||
double mhz = std::stod(fields["cpu MHz"]);
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%.3f", mhz / 1000.0);
|
||||
p.kv[K_SPEED_GHZ] = buf;
|
||||
} catch (...) {}
|
||||
}
|
||||
if (fields.count("cpu cores")) p.kv[K_CORES] = fields["cpu cores"];
|
||||
if (fields.count("siblings")) p.kv[K_THREADS] = fields["siblings"];
|
||||
if (fields.count("cache size")) {
|
||||
// "9216 KB"
|
||||
uint32_t kb = static_cast<uint32_t>(leading_uint(fields["cache size"]));
|
||||
if (kb > 0) p.kv[K_CACHE_L3_KB] = std::to_string(kb);
|
||||
}
|
||||
// Manufacturer from model name heuristic
|
||||
if (fields.count("model name")) {
|
||||
std::string lower = fields["model name"];
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||||
if (lower.find("intel") != std::string::npos) p.kv[K_MANUFACTURER] = "Intel";
|
||||
else if (lower.find("amd") != std::string::npos) p.kv[K_MANUFACTURER] = "AMD";
|
||||
}
|
||||
// Use "Proc <N>" style socket designation based on physical id order
|
||||
p.kv[K_SOCKET] = "Proc " + std::to_string(phys_id + 1);
|
||||
result.push_back(std::move(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
|
|
@ -1199,3 +1419,162 @@ std::vector<DiscoveredPart> Discovery::discover_nics() {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// GPUs
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
std::vector<DiscoveredPart> Discovery::discover_gpus() {
|
||||
std::vector<DiscoveredPart> result;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// macOS GPU discovery not implemented
|
||||
return result;
|
||||
#else
|
||||
std::string mm_out = run_cmd("lspci -mm 2>/dev/null");
|
||||
if (mm_out.empty()) return result;
|
||||
|
||||
// Parse lspci -mm output. Each line:
|
||||
// slot "class" "vendor" "device" ...
|
||||
// (fields separated by spaces, quoted with double quotes)
|
||||
auto parse_lspci_mm_line = [](const std::string& line)
|
||||
-> std::vector<std::string> {
|
||||
std::vector<std::string> fields;
|
||||
size_t i = 0;
|
||||
while (i < line.size()) {
|
||||
while (i < line.size() && std::isspace((unsigned char)line[i])) ++i;
|
||||
if (i >= line.size()) break;
|
||||
if (line[i] == '"') {
|
||||
++i;
|
||||
std::string tok;
|
||||
while (i < line.size() && line[i] != '"') tok += line[i++];
|
||||
if (i < line.size()) ++i;
|
||||
fields.push_back(tok);
|
||||
} else {
|
||||
std::string tok;
|
||||
while (i < line.size() && !std::isspace((unsigned char)line[i])) tok += line[i++];
|
||||
fields.push_back(tok);
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
};
|
||||
|
||||
std::istringstream ss(mm_out);
|
||||
std::string line;
|
||||
while (std::getline(ss, line)) {
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
if (line.empty()) continue;
|
||||
|
||||
auto fields = parse_lspci_mm_line(line);
|
||||
if (fields.size() < 4) continue;
|
||||
|
||||
std::string slot = fields[0];
|
||||
std::string cls = fields[1];
|
||||
std::string vendor = fields[2];
|
||||
std::string device = fields[3];
|
||||
|
||||
// Filter for GPU classes
|
||||
bool is_gpu = (cls.find("VGA compatible controller") != std::string::npos ||
|
||||
cls.find("3D controller") != std::string::npos ||
|
||||
cls.find("Display controller") != std::string::npos);
|
||||
if (!is_gpu) continue;
|
||||
|
||||
DiscoveredPart p;
|
||||
p.type_name = PTYPE_GPU;
|
||||
p.kv[K_MANUFACTURER] = vendor;
|
||||
p.kv[K_MODEL] = device;
|
||||
p.kv[K_SERIAL] = slot; // PCI slot as stable ID
|
||||
|
||||
// NVIDIA VRAM via nvidia-smi
|
||||
std::string lower_vendor = vendor;
|
||||
std::transform(lower_vendor.begin(), lower_vendor.end(), lower_vendor.begin(), ::tolower);
|
||||
if (lower_vendor.find("nvidia") != std::string::npos) {
|
||||
std::string nsmi = run_cmd(
|
||||
"nvidia-smi --query-gpu=index,name,memory.total --format=csv,noheader 2>/dev/null");
|
||||
if (!nsmi.empty()) {
|
||||
std::istringstream ns(nsmi);
|
||||
std::string nline;
|
||||
while (std::getline(ns, nline)) {
|
||||
if (!nline.empty() && nline.back() == '\r') nline.pop_back();
|
||||
size_t last_comma = nline.rfind(',');
|
||||
if (last_comma == std::string::npos) continue;
|
||||
std::string mem_str = trim(nline.substr(last_comma + 1));
|
||||
uint64_t vram_mb = leading_uint(mem_str);
|
||||
if (vram_mb > 0) {
|
||||
p.kv[K_VRAM_MB] = std::to_string(vram_mb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find DRM card for this PCI slot
|
||||
std::vector<std::string> outputs;
|
||||
for (int card_idx = 0; card_idx < 8; ++card_idx) {
|
||||
std::string card_name = "card" + std::to_string(card_idx);
|
||||
std::string dev_link_path = "/sys/class/drm/" + card_name + "/device";
|
||||
std::string resolved = run_cmd("readlink -f " + dev_link_path + " 2>/dev/null");
|
||||
while (!resolved.empty() && std::isspace((unsigned char)resolved.back()))
|
||||
resolved.pop_back();
|
||||
if (resolved.empty()) continue;
|
||||
|
||||
bool matches = (resolved.find(slot) != std::string::npos);
|
||||
if (!matches && slot.size() > 5 && slot[4] == ':') {
|
||||
std::string short_addr = slot.substr(5);
|
||||
matches = (resolved.find(short_addr) != std::string::npos);
|
||||
}
|
||||
if (!matches) continue;
|
||||
|
||||
// Found the card; check AMD VRAM
|
||||
if (p.kv.find(K_VRAM_MB) == p.kv.end()) {
|
||||
std::string vram_path = "/sys/class/drm/" + card_name + "/device/mem_info_vram_total";
|
||||
FILE* vf = fopen(vram_path.c_str(), "r");
|
||||
if (vf) {
|
||||
uint64_t vram_bytes = 0;
|
||||
fscanf(vf, "%llu", (unsigned long long*)&vram_bytes);
|
||||
fclose(vf);
|
||||
if (vram_bytes > 0) {
|
||||
p.kv[K_VRAM_MB] = std::to_string(vram_bytes / (1024 * 1024));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate connectors
|
||||
std::string ls_out = run_cmd("ls /sys/class/drm/ 2>/dev/null");
|
||||
if (!ls_out.empty()) {
|
||||
std::istringstream lss(ls_out);
|
||||
std::string entry;
|
||||
while (lss >> entry) {
|
||||
std::string prefix = card_name + "-";
|
||||
if (entry.rfind(prefix, 0) != 0) continue;
|
||||
std::string connector_name = entry.substr(prefix.size());
|
||||
std::string status_path = "/sys/class/drm/" + entry + "/status";
|
||||
FILE* sf = fopen(status_path.c_str(), "r");
|
||||
std::string status = "unknown";
|
||||
if (sf) {
|
||||
char sbuf[32] = {};
|
||||
fscanf(sf, "%31s", sbuf);
|
||||
fclose(sf);
|
||||
status = sbuf;
|
||||
}
|
||||
outputs.push_back(connector_name + ":" + status);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!outputs.empty()) {
|
||||
std::string out_str;
|
||||
for (size_t oi = 0; oi < outputs.size(); ++oi) {
|
||||
if (oi) out_str += LS;
|
||||
out_str += outputs[oi];
|
||||
}
|
||||
p.kv[K_DISPLAY_OUTPUTS] = out_str;
|
||||
}
|
||||
|
||||
result.push_back(std::move(p));
|
||||
}
|
||||
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ private:
|
|||
std::vector<DiscoveredPart> discover_cpu_slots();
|
||||
std::vector<DiscoveredPart> discover_disks();
|
||||
std::vector<DiscoveredPart> discover_nics();
|
||||
std::vector<DiscoveredPart> discover_gpus();
|
||||
|
||||
// Run a shell command, return trimmed stdout (≤64 KB). "" on failure.
|
||||
static std::string run_cmd(const std::string& cmd);
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
const std::vector<std::string> order = {
|
||||
PTYPE_CPU, PTYPE_CPU_SLOT,
|
||||
PTYPE_MEMORY_STICK, PTYPE_MEMORY_SLOT,
|
||||
PTYPE_DISK, PTYPE_NIC
|
||||
PTYPE_DISK, PTYPE_NIC, PTYPE_GPU
|
||||
};
|
||||
static const std::map<std::string, std::string> labels = {
|
||||
{PTYPE_CPU, "CPUs"},
|
||||
|
|
@ -254,6 +254,7 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
{PTYPE_MEMORY_SLOT, "Memory Slots"},
|
||||
{PTYPE_DISK, "Disks"},
|
||||
{PTYPE_NIC, "NICs"},
|
||||
{PTYPE_GPU, "GPUs"},
|
||||
};
|
||||
|
||||
// Collect sections that actually have data
|
||||
|
|
@ -277,6 +278,7 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
bool item_last = (ii + 1 == items.size());
|
||||
auto& m = items[ii]->kv;
|
||||
std::string line;
|
||||
std::vector<std::string> extra_lines;
|
||||
|
||||
if (type == PTYPE_CPU) {
|
||||
std::string sock = kv_get(m, K_SOCKET);
|
||||
|
|
@ -286,12 +288,46 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
std::string threads = kv_get(m, K_THREADS);
|
||||
if (!sock.empty()) line += "[" + sock + "] ";
|
||||
line += name.empty() ? "(unknown)" : name;
|
||||
// Only append speed if the name doesn't already embed it (e.g. "@ 2.27GHz")
|
||||
if (!speed.empty() && name.find('@') == std::string::npos)
|
||||
line += " @ " + speed + " GHz";
|
||||
if (!cores.empty() || !threads.empty())
|
||||
line += " · " + (cores.empty() ? "?" : cores)
|
||||
+ " cores / " + (threads.empty() ? "?" : threads) + " threads";
|
||||
// Secondary line: socket type, max speed, bus, voltage
|
||||
{
|
||||
std::string sock_type = kv_get(m, K_SOCKET_TYPE);
|
||||
std::string max_spd = kv_get(m, K_MAX_SPEED_GHZ);
|
||||
std::string bus = kv_get(m, K_BUS_MHZ);
|
||||
std::string volt = kv_get(m, K_VOLTAGE_V);
|
||||
std::string sline2;
|
||||
if (!sock_type.empty()) sline2 += "Socket: " + sock_type;
|
||||
if (!max_spd.empty() && max_spd != "0.000000") {
|
||||
if (!sline2.empty()) sline2 += " · ";
|
||||
sline2 += "Max: " + max_spd + " GHz";
|
||||
}
|
||||
if (!bus.empty() && bus != "0") {
|
||||
if (!sline2.empty()) sline2 += " · ";
|
||||
sline2 += "Bus: " + bus + " MHz";
|
||||
}
|
||||
if (!volt.empty() && volt != "0.0") {
|
||||
if (!sline2.empty()) sline2 += " · ";
|
||||
sline2 += "Voltage: " + volt + " V";
|
||||
}
|
||||
if (!sline2.empty()) extra_lines.push_back(sline2);
|
||||
}
|
||||
// Third line: cache
|
||||
{
|
||||
std::string l1 = kv_get(m, K_CACHE_L1_KB);
|
||||
std::string l2 = kv_get(m, K_CACHE_L2_KB);
|
||||
std::string l3 = kv_get(m, K_CACHE_L3_KB);
|
||||
if (!l1.empty() || !l2.empty() || !l3.empty()) {
|
||||
std::string cline = "Cache:";
|
||||
if (!l1.empty() && l1 != "0") cline += " L1 " + l1 + " KB";
|
||||
if (!l2.empty() && l2 != "0") cline += " L2 " + l2 + " KB";
|
||||
if (!l3.empty() && l3 != "0") cline += " L3 " + l3 + " KB";
|
||||
extra_lines.push_back(cline);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type == PTYPE_CPU_SLOT) {
|
||||
std::string sock = kv_get(m, K_SOCKET);
|
||||
|
|
@ -304,6 +340,12 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
std::string size = kv_get(m, K_SIZE_MB);
|
||||
std::string mfr = kv_get(m, K_MANUFACTURER);
|
||||
std::string speed = kv_get(m, K_SPEED_MHZ);
|
||||
std::string memtype = kv_get(m, K_MEM_TYPE);
|
||||
std::string ff = kv_get(m, K_FORM_FACTOR);
|
||||
std::string rank = kv_get(m, K_RANK);
|
||||
std::string dw = kv_get(m, K_DATA_WIDTH);
|
||||
std::string bw = kv_get(m, K_BANDWIDTH_MBPS);
|
||||
std::string pn = kv_get(m, K_PART_NUMBER);
|
||||
if (!loc.empty()) line += "[" + loc + "] ";
|
||||
if (!size.empty()) {
|
||||
uint64_t mb = std::stoull(size);
|
||||
|
|
@ -312,8 +354,19 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
else
|
||||
line += std::to_string(mb) + " MB";
|
||||
}
|
||||
if (!memtype.empty()) line += " " + memtype;
|
||||
if (!ff.empty()) line += " " + ff;
|
||||
if (!rank.empty() && rank != "0") line += " Rank " + rank;
|
||||
if (!dw.empty() && dw != "0") line += " " + dw + "-bit";
|
||||
if (!speed.empty() && speed != "0") line += " @ " + speed + " MHz";
|
||||
if (!bw.empty() && bw != "0") {
|
||||
char bwbuf[32];
|
||||
uint64_t bwval = std::stoull(bw);
|
||||
snprintf(bwbuf, sizeof(bwbuf), " \xe2\x89\x88 %llu MB/s", (unsigned long long)bwval);
|
||||
line += bwbuf;
|
||||
}
|
||||
if (!mfr.empty()) line += " " + mfr;
|
||||
if (!speed.empty()) line += " @ " + speed + " MHz";
|
||||
if (!pn.empty()) line += " " + pn;
|
||||
|
||||
} else if (type == PTYPE_MEMORY_SLOT) {
|
||||
std::string loc = kv_get(m, K_LOCATOR);
|
||||
|
|
@ -360,12 +413,34 @@ static int cmd_discover_only(const std::string& filter_type) {
|
|||
if (!ips.empty()) line += ls_join(ips) + " ";
|
||||
if (!speed.empty() && speed != "0")
|
||||
line += speed + " Mbps";
|
||||
|
||||
} else if (type == PTYPE_GPU) {
|
||||
std::string mfr = kv_get(m, K_MANUFACTURER);
|
||||
std::string model = kv_get(m, K_MODEL);
|
||||
std::string serial = kv_get(m, K_SERIAL);
|
||||
std::string vram = kv_get(m, K_VRAM_MB);
|
||||
if (!mfr.empty()) line += mfr + " ";
|
||||
if (!model.empty()) line += model;
|
||||
if (!serial.empty()) line += " [" + serial + "]";
|
||||
if (!vram.empty() && vram != "0") line += " " + vram + " MB VRAM";
|
||||
// Outputs as secondary line
|
||||
std::string outputs = kv_get(m, K_DISPLAY_OUTPUTS);
|
||||
if (!outputs.empty())
|
||||
extra_lines.push_back("Outputs: " + ls_join(outputs));
|
||||
else
|
||||
extra_lines.push_back("Outputs: (none)");
|
||||
}
|
||||
|
||||
// Trim trailing spaces
|
||||
while (!line.empty() && line.back() == ' ') line.pop_back();
|
||||
|
||||
std::cout << child_pfx << (item_last ? "└── " : "├── ") << line << "\n";
|
||||
|
||||
// Print extra lines (indented further)
|
||||
std::string extra_pfx = child_pfx + (item_last ? " " : "│ ") + " ";
|
||||
for (auto& el : extra_lines) {
|
||||
std::cout << extra_pfx << el << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,12 @@ struct MemoryStick : PartBase {
|
|||
std::string channel_config;
|
||||
std::string other_info;
|
||||
std::string locator;
|
||||
std::string memory_type;
|
||||
std::string form_factor;
|
||||
std::string part_number;
|
||||
uint32_t rank = 0;
|
||||
uint32_t data_width_bits = 0;
|
||||
uint64_t bandwidth_mbps = 0;
|
||||
};
|
||||
|
||||
struct MemorySlot : PartBase {
|
||||
|
|
@ -65,6 +71,13 @@ struct CPU : PartBase {
|
|||
uint32_t cores = 0;
|
||||
uint32_t threads = 0;
|
||||
std::string socket_designation;
|
||||
std::string socket_type;
|
||||
double max_speed_ghz = 0.0;
|
||||
uint32_t bus_speed_mhz = 0;
|
||||
uint32_t cache_l1_kb = 0;
|
||||
uint32_t cache_l2_kb = 0;
|
||||
uint32_t cache_l3_kb = 0;
|
||||
double voltage_v = 0.0;
|
||||
};
|
||||
|
||||
struct CPUSlot : PartBase {
|
||||
|
|
@ -104,6 +117,14 @@ struct NetworkCard : PartBase {
|
|||
bool dhcp = false;
|
||||
};
|
||||
|
||||
struct GPU : PartBase {
|
||||
std::string manufacturer;
|
||||
std::string model;
|
||||
uint64_t vram_mb = 0;
|
||||
uint64_t bandwidth_mbps = 0;
|
||||
std::vector<std::string> display_outputs;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// In-memory inventory
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -118,6 +139,7 @@ struct Inventory {
|
|||
std::vector<CPUSlot> cpu_slots;
|
||||
std::vector<Disk> disks;
|
||||
std::vector<NetworkCard> network_cards;
|
||||
std::vector<GPU> gpus;
|
||||
|
||||
uint32_t next_server_id = 1;
|
||||
uint32_t next_part_type_id = 1;
|
||||
|
|
@ -134,3 +156,4 @@ constexpr const char* PTYPE_CPU = "cpu";
|
|||
constexpr const char* PTYPE_CPU_SLOT = "cpu_slot";
|
||||
constexpr const char* PTYPE_DISK = "disk";
|
||||
constexpr const char* PTYPE_NIC = "nic";
|
||||
constexpr const char* PTYPE_GPU = "gpu";
|
||||
|
|
|
|||
|
|
@ -105,6 +105,15 @@ constexpr const char* K_SPEED_GHZ = "speed_ghz";
|
|||
constexpr const char* K_CORES = "cores";
|
||||
constexpr const char* K_THREADS = "threads";
|
||||
|
||||
// CPU extended
|
||||
constexpr const char* K_MAX_SPEED_GHZ = "max_speed_ghz";
|
||||
constexpr const char* K_BUS_MHZ = "bus_speed_mhz";
|
||||
constexpr const char* K_CACHE_L1_KB = "cache_l1_kb";
|
||||
constexpr const char* K_CACHE_L2_KB = "cache_l2_kb";
|
||||
constexpr const char* K_CACHE_L3_KB = "cache_l3_kb";
|
||||
constexpr const char* K_VOLTAGE_V = "voltage_v";
|
||||
constexpr const char* K_SOCKET_TYPE = "socket_type";
|
||||
|
||||
// CPUSlot
|
||||
constexpr const char* K_FORM_FACTOR = "form_factor";
|
||||
constexpr const char* K_INSTALLED_CPU = "installed_cpu_id";
|
||||
|
|
@ -130,6 +139,18 @@ constexpr const char* K_MAC = "mac_address";
|
|||
constexpr const char* K_IPS = "ip_addresses"; // LS-separated
|
||||
constexpr const char* K_DHCP = "dhcp";
|
||||
|
||||
// MemoryStick extended
|
||||
constexpr const char* K_MEM_TYPE = "memory_type";
|
||||
constexpr const char* K_PART_NUMBER = "part_number";
|
||||
constexpr const char* K_RANK = "rank";
|
||||
constexpr const char* K_DATA_WIDTH = "data_width_bits";
|
||||
constexpr const char* K_BANDWIDTH_MBPS = "bandwidth_mbps";
|
||||
|
||||
// GPU
|
||||
constexpr const char* K_VRAM_MB = "vram_mb";
|
||||
constexpr const char* K_GPU_BANDWIDTH_MBPS = "gpu_bandwidth_mbps";
|
||||
constexpr const char* K_DISPLAY_OUTPUTS = "display_outputs";
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -110,6 +110,12 @@ static void apply_memory_stick(MemoryStick& m, const std::map<std::string,std::s
|
|||
if (auto it = kv.find(K_CHANNEL_CONFIG); it != kv.end()) m.channel_config = it->second;
|
||||
if (auto it = kv.find(K_OTHER_INFO); it != kv.end()) m.other_info = it->second;
|
||||
if (auto it = kv.find(K_LOCATOR); it != kv.end()) m.locator = it->second;
|
||||
if (auto it = kv.find(K_MEM_TYPE); it != kv.end()) m.memory_type = it->second;
|
||||
if (auto it = kv.find(K_FORM_FACTOR); it != kv.end()) m.form_factor = it->second;
|
||||
if (auto it = kv.find(K_PART_NUMBER); it != kv.end()) m.part_number = it->second;
|
||||
if (auto it = kv.find(K_RANK); it != kv.end()) m.rank = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_DATA_WIDTH); it != kv.end()) m.data_width_bits = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_BANDWIDTH_MBPS); it != kv.end()) m.bandwidth_mbps = std::stoull(it->second);
|
||||
}
|
||||
|
||||
static void apply_memory_slot(MemorySlot& m, const std::map<std::string,std::string>& kv) {
|
||||
|
|
@ -131,6 +137,13 @@ static void apply_cpu(CPU& c, const std::map<std::string,std::string>& kv) {
|
|||
if (auto it = kv.find(K_CORES); it != kv.end()) c.cores = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_THREADS); it != kv.end()) c.threads = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_SOCKET); it != kv.end()) c.socket_designation = it->second;
|
||||
if (auto it = kv.find(K_SOCKET_TYPE); it != kv.end()) c.socket_type = it->second;
|
||||
if (auto it = kv.find(K_MAX_SPEED_GHZ); it != kv.end()) c.max_speed_ghz = std::stod(it->second);
|
||||
if (auto it = kv.find(K_BUS_MHZ); it != kv.end()) c.bus_speed_mhz = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_CACHE_L1_KB); it != kv.end()) c.cache_l1_kb = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_CACHE_L2_KB); it != kv.end()) c.cache_l2_kb = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_CACHE_L3_KB); it != kv.end()) c.cache_l3_kb = static_cast<uint32_t>(std::stoul(it->second));
|
||||
if (auto it = kv.find(K_VOLTAGE_V); it != kv.end()) c.voltage_v = std::stod(it->second);
|
||||
}
|
||||
|
||||
static void apply_cpu_slot(CPUSlot& c, const std::map<std::string,std::string>& kv) {
|
||||
|
|
@ -173,6 +186,15 @@ static void apply_nic(NetworkCard& n, const std::map<std::string,std::string>& k
|
|||
if (auto it = kv.find(K_DHCP); it != kv.end()) n.dhcp = (it->second == "1" || it->second == "true");
|
||||
}
|
||||
|
||||
static void apply_gpu(GPU& g, const std::map<std::string,std::string>& kv) {
|
||||
apply_base(g, kv);
|
||||
if (auto it = kv.find(K_MANUFACTURER); it != kv.end()) g.manufacturer = it->second;
|
||||
if (auto it = kv.find(K_MODEL); it != kv.end()) g.model = it->second;
|
||||
if (auto it = kv.find(K_VRAM_MB); it != kv.end()) g.vram_mb = std::stoull(it->second);
|
||||
if (auto it = kv.find(K_GPU_BANDWIDTH_MBPS); it != kv.end()) g.bandwidth_mbps = std::stoull(it->second);
|
||||
if (auto it = kv.find(K_DISPLAY_OUTPUTS); it != kv.end()) g.display_outputs = ls_split(it->second);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Wire-row serializers
|
||||
// type_name<FS>part_id<FS>server_id<FS>serial_or_NULL<FS>last_updated<FS>k1<FS>v1...
|
||||
|
|
@ -193,6 +215,12 @@ std::string Database::serialize_memory_stick(const MemoryStick& m) const {
|
|||
+ FS + K_CHANNEL_CONFIG + FS + m.channel_config
|
||||
+ FS + K_OTHER_INFO + FS + m.other_info
|
||||
+ FS + K_LOCATOR + FS + m.locator
|
||||
+ FS + K_MEM_TYPE + FS + m.memory_type
|
||||
+ FS + K_FORM_FACTOR + FS + m.form_factor
|
||||
+ FS + K_PART_NUMBER + FS + m.part_number
|
||||
+ FS + K_RANK + FS + std::to_string(m.rank)
|
||||
+ FS + K_DATA_WIDTH + FS + std::to_string(m.data_width_bits)
|
||||
+ FS + K_BANDWIDTH_MBPS + FS + std::to_string(m.bandwidth_mbps)
|
||||
+ "\n";
|
||||
}
|
||||
|
||||
|
|
@ -215,6 +243,13 @@ std::string Database::serialize_cpu(const CPU& c) const {
|
|||
+ FS + K_CORES + FS + std::to_string(c.cores)
|
||||
+ FS + K_THREADS + FS + std::to_string(c.threads)
|
||||
+ FS + K_SOCKET + FS + c.socket_designation
|
||||
+ FS + K_SOCKET_TYPE + FS + c.socket_type
|
||||
+ FS + K_MAX_SPEED_GHZ + FS + std::to_string(c.max_speed_ghz)
|
||||
+ FS + K_BUS_MHZ + FS + std::to_string(c.bus_speed_mhz)
|
||||
+ FS + K_CACHE_L1_KB + FS + std::to_string(c.cache_l1_kb)
|
||||
+ FS + K_CACHE_L2_KB + FS + std::to_string(c.cache_l2_kb)
|
||||
+ FS + K_CACHE_L3_KB + FS + std::to_string(c.cache_l3_kb)
|
||||
+ FS + K_VOLTAGE_V + FS + std::to_string(c.voltage_v)
|
||||
+ "\n";
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +321,28 @@ std::string Database::serialize_nic(const NetworkCard& n) const {
|
|||
+ "\n";
|
||||
}
|
||||
|
||||
std::string Database::serialize_gpu(const GPU& g) const {
|
||||
auto esc_join = [](const std::vector<std::string>& v) -> std::string {
|
||||
std::string out;
|
||||
for (size_t i = 0; i < v.size(); ++i) {
|
||||
if (i) out += LS;
|
||||
for (char c : v[i]) {
|
||||
if (c == '\\') out += "\\\\";
|
||||
else if (c == '|') out += "\\|";
|
||||
else out += c;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
return base_prefix(PTYPE_GPU, g)
|
||||
+ FS + K_MANUFACTURER + FS + g.manufacturer
|
||||
+ FS + K_MODEL + FS + g.model
|
||||
+ FS + K_VRAM_MB + FS + std::to_string(g.vram_mb)
|
||||
+ FS + K_GPU_BANDWIDTH_MBPS + FS + std::to_string(g.bandwidth_mbps)
|
||||
+ FS + K_DISPLAY_OUTPUTS + FS + esc_join(g.display_outputs)
|
||||
+ "\n";
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Constructor
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -347,7 +404,13 @@ bool Database::save_nolock() {
|
|||
<< "|" << escape(m.manufacturer)
|
||||
<< "|" << escape(m.channel_config)
|
||||
<< "|" << escape(m.other_info)
|
||||
<< "|" << escape(m.locator) << "\n";
|
||||
<< "|" << escape(m.locator)
|
||||
<< "|" << escape(m.memory_type)
|
||||
<< "|" << escape(m.form_factor)
|
||||
<< "|" << escape(m.part_number)
|
||||
<< "|" << m.rank
|
||||
<< "|" << m.data_width_bits
|
||||
<< "|" << m.bandwidth_mbps << "\n";
|
||||
}
|
||||
for (const auto& m : inv_.memory_slots) {
|
||||
std::string inst = m.installed_stick_id.has_value()
|
||||
|
|
@ -369,7 +432,14 @@ bool Database::save_nolock() {
|
|||
<< "|" << c.speed_ghz
|
||||
<< "|" << c.cores
|
||||
<< "|" << c.threads
|
||||
<< "|" << escape(c.socket_designation) << "\n";
|
||||
<< "|" << escape(c.socket_designation)
|
||||
<< "|" << escape(c.socket_type)
|
||||
<< "|" << c.max_speed_ghz
|
||||
<< "|" << c.bus_speed_mhz
|
||||
<< "|" << c.cache_l1_kb
|
||||
<< "|" << c.cache_l2_kb
|
||||
<< "|" << c.cache_l3_kb
|
||||
<< "|" << c.voltage_v << "\n";
|
||||
}
|
||||
for (const auto& c : inv_.cpu_slots) {
|
||||
std::string inst = c.installed_cpu_id.has_value()
|
||||
|
|
@ -423,6 +493,17 @@ bool Database::save_nolock() {
|
|||
write_str_list(f, n.ip_addresses);
|
||||
f << "|" << (n.dhcp ? 1 : 0) << "\n";
|
||||
}
|
||||
for (const auto& g : inv_.gpus) {
|
||||
f << "GPU|" << g.part_id << "|" << g.server_id
|
||||
<< "|" << escape(base_serial(g))
|
||||
<< "|" << g.last_updated
|
||||
<< "|" << escape(g.manufacturer)
|
||||
<< "|" << escape(g.model)
|
||||
<< "|" << g.vram_mb
|
||||
<< "|" << g.bandwidth_mbps << "|";
|
||||
write_str_list(f, g.display_outputs);
|
||||
f << "\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -502,6 +583,12 @@ bool Database::load() {
|
|||
m.channel_config = get(8);
|
||||
m.other_info = get(9);
|
||||
m.locator = get(10);
|
||||
m.memory_type = get(11);
|
||||
m.form_factor = get(12);
|
||||
m.part_number = get(13);
|
||||
m.rank = getu32(14);
|
||||
m.data_width_bits = getu32(15);
|
||||
m.bandwidth_mbps = getu64(16);
|
||||
fresh.memory_sticks.push_back(std::move(m));
|
||||
} else if (rec == "MSLOT" && flds.size() >= 8) {
|
||||
MemorySlot m;
|
||||
|
|
@ -526,6 +613,13 @@ bool Database::load() {
|
|||
c.cores = getu32(8);
|
||||
c.threads = getu32(9);
|
||||
c.socket_designation = get(10);
|
||||
c.socket_type = get(11);
|
||||
c.max_speed_ghz = flds.size() > 12 && !flds[12].empty() ? std::stod(flds[12]) : 0.0;
|
||||
c.bus_speed_mhz = getu32(13);
|
||||
c.cache_l1_kb = getu32(14);
|
||||
c.cache_l2_kb = getu32(15);
|
||||
c.cache_l3_kb = getu32(16);
|
||||
c.voltage_v = flds.size() > 17 && !flds[17].empty() ? std::stod(flds[17]) : 0.0;
|
||||
fresh.cpus.push_back(std::move(c));
|
||||
} else if (rec == "CPUSLOT" && flds.size() >= 7) {
|
||||
CPUSlot c;
|
||||
|
|
@ -598,6 +692,22 @@ bool Database::load() {
|
|||
}
|
||||
n.dhcp = (flds.size() > 12 && flds[12] == "1");
|
||||
fresh.network_cards.push_back(std::move(n));
|
||||
} else if (rec == "GPU" && flds.size() >= 9) {
|
||||
GPU g;
|
||||
g.part_id = getu32(1);
|
||||
g.server_id = getu32(2);
|
||||
g.serial_number = get_serial(3);
|
||||
g.last_updated = geti64(4);
|
||||
g.manufacturer = get(5);
|
||||
g.model = get(6);
|
||||
g.vram_mb = getu64(7);
|
||||
g.bandwidth_mbps = getu64(8);
|
||||
// display_outputs: LS separated in flds[9]
|
||||
if (flds.size() > 9) {
|
||||
for (auto& e : split(flds[9], LS))
|
||||
g.display_outputs.push_back(unescape(e));
|
||||
}
|
||||
fresh.gpus.push_back(std::move(g));
|
||||
}
|
||||
}
|
||||
inv_ = std::move(fresh);
|
||||
|
|
@ -739,6 +849,10 @@ uint32_t Database::add_part_nolock(const std::string& type_name, uint32_t server
|
|||
NetworkCard n; n.part_id = pid; n.server_id = server_id; n.last_updated = now;
|
||||
apply_nic(n, kv);
|
||||
inv_.network_cards.push_back(std::move(n));
|
||||
} else if (type_name == PTYPE_GPU) {
|
||||
GPU g; g.part_id = pid; g.server_id = server_id; g.last_updated = now;
|
||||
apply_gpu(g, kv);
|
||||
inv_.gpus.push_back(std::move(g));
|
||||
} else {
|
||||
// Unknown type — no-op, reclaim the id by decrementing
|
||||
--inv_.next_part_id;
|
||||
|
|
@ -796,6 +910,8 @@ uint32_t Database::upsert_part(const std::string& type_name, uint32_t server_id,
|
|||
apply_cpu_slot(p, kv);
|
||||
else if constexpr (std::is_same_v<std::decay_t<decltype(p)>, Disk>)
|
||||
apply_disk(p, kv);
|
||||
else if constexpr (std::is_same_v<std::decay_t<decltype(p)>, GPU>)
|
||||
apply_gpu(p, kv);
|
||||
p.last_updated = now;
|
||||
save_nolock();
|
||||
return p.part_id;
|
||||
|
|
@ -810,6 +926,7 @@ uint32_t Database::upsert_part(const std::string& type_name, uint32_t server_id,
|
|||
else if (type_name == PTYPE_CPU) found = try_update(inv_.cpus);
|
||||
else if (type_name == PTYPE_CPU_SLOT) found = try_update(inv_.cpu_slots);
|
||||
else if (type_name == PTYPE_DISK) found = try_update(inv_.disks);
|
||||
else if (type_name == PTYPE_GPU) found = try_update(inv_.gpus);
|
||||
if (found) return found;
|
||||
}
|
||||
// Natural key fallback when no serial — prevents duplicate inserts on each run
|
||||
|
|
@ -866,6 +983,22 @@ uint32_t Database::upsert_part(const std::string& type_name, uint32_t server_id,
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (type_name == PTYPE_GPU) {
|
||||
auto mfr_it = kv.find(K_MANUFACTURER);
|
||||
auto mod_it = kv.find(K_MODEL);
|
||||
if (mod_it != kv.end() && !mod_it->second.empty()) {
|
||||
const std::string& model = mod_it->second;
|
||||
const std::string& mfr = (mfr_it != kv.end()) ? mfr_it->second : "";
|
||||
for (auto& g : inv_.gpus) {
|
||||
if (g.server_id == server_id && g.model == model &&
|
||||
(mfr.empty() || g.manufacturer == mfr)) {
|
||||
apply_gpu(g, kv);
|
||||
g.last_updated = now;
|
||||
save_nolock();
|
||||
return g.part_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Not found — insert
|
||||
|
|
@ -896,6 +1029,7 @@ bool Database::edit_part(uint32_t part_id, const std::map<std::string,std::strin
|
|||
if (try_edit(inv_.cpu_slots, apply_cpu_slot)) return true;
|
||||
if (try_edit(inv_.disks, apply_disk)) return true;
|
||||
if (try_edit(inv_.network_cards, apply_nic)) return true;
|
||||
if (try_edit(inv_.gpus, apply_gpu)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -918,7 +1052,8 @@ bool Database::remove_part(uint32_t part_id) {
|
|||
|| try_remove(inv_.cpus)
|
||||
|| try_remove(inv_.cpu_slots)
|
||||
|| try_remove(inv_.disks)
|
||||
|| try_remove(inv_.network_cards);
|
||||
|| try_remove(inv_.network_cards)
|
||||
|| try_remove(inv_.gpus);
|
||||
if (removed) save_nolock();
|
||||
return removed;
|
||||
}
|
||||
|
|
@ -956,6 +1091,10 @@ std::vector<std::string> Database::list_parts(uint32_t server_id,
|
|||
for (const auto& n : inv_.network_cards)
|
||||
if (n.server_id == server_id) rows.push_back(serialize_nic(n));
|
||||
|
||||
if (all || type_filter == PTYPE_GPU)
|
||||
for (const auto& g : inv_.gpus)
|
||||
if (g.server_id == server_id) rows.push_back(serialize_gpu(g));
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
|
@ -974,6 +1113,8 @@ std::string Database::get_part_row(uint32_t part_id) const {
|
|||
if (d.part_id == part_id) return serialize_disk(d);
|
||||
for (const auto& n : inv_.network_cards)
|
||||
if (n.part_id == part_id) return serialize_nic(n);
|
||||
for (const auto& g : inv_.gpus)
|
||||
if (g.part_id == part_id) return serialize_gpu(g);
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,4 +95,5 @@ private:
|
|||
std::string serialize_cpu_slot (const CPUSlot& c) const;
|
||||
std::string serialize_disk (const Disk& d) const;
|
||||
std::string serialize_nic (const NetworkCard& n) const;
|
||||
std::string serialize_gpu (const GPU& g) const;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue