Add CPU/RAM and GPU model telemetry
Extend telemetry collection to include GPU model, CPU vendor/model, and RAM speed. misc/api.func: enhance detect_gpu to capture GPU_MODEL and default unknown values; add detect_cpu and detect_ram (dmidecode used for RAM speed), export new globals, and include cpu_*/gpu_model/ram_speed in post_to_api and post_update_to_api payloads. misc/data/service.go: add GPUModel/CPUVendor/CPUModel/RAMSpeed fields to TelemetryIn/Out/StatusUpdate, update PBClient mapping, expand allowed enum values to accept "unknown", sanitize and default empty vendor/passthrough fields to "unknown", and validate new cpu_vendor values. Changes maintain backward compatibility by using "unknown" where data is unavailable.
This commit is contained in:
parent
1dcd83abea
commit
18fa3ec4e9
143
misc/api.func
143
misc/api.func
@ -171,38 +171,97 @@ explain_exit_code() {
|
||||
# ------------------------------------------------------------------------------
|
||||
# detect_gpu()
|
||||
#
|
||||
# - Detects GPU vendor and passthrough type
|
||||
# - Sets GPU_VENDOR and GPU_PASSTHROUGH globals
|
||||
# - Detects GPU vendor, model, and passthrough type
|
||||
# - Sets GPU_VENDOR, GPU_MODEL, and GPU_PASSTHROUGH globals
|
||||
# - Used for GPU analytics
|
||||
# ------------------------------------------------------------------------------
|
||||
detect_gpu() {
|
||||
GPU_VENDOR=""
|
||||
GPU_PASSTHROUGH="none"
|
||||
GPU_VENDOR="unknown"
|
||||
GPU_MODEL=""
|
||||
GPU_PASSTHROUGH="unknown"
|
||||
|
||||
# Detect Intel GPU
|
||||
if lspci 2>/dev/null | grep -qi "VGA.*Intel"; then
|
||||
GPU_VENDOR="intel"
|
||||
GPU_PASSTHROUGH="igpu"
|
||||
fi
|
||||
local gpu_line
|
||||
gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1)
|
||||
|
||||
# Detect AMD GPU
|
||||
if lspci 2>/dev/null | grep -qi "VGA.*AMD\|VGA.*ATI"; then
|
||||
GPU_VENDOR="amd"
|
||||
# Check if discrete
|
||||
if lspci 2>/dev/null | grep -qi "AMD.*Radeon"; then
|
||||
GPU_PASSTHROUGH="dgpu"
|
||||
else
|
||||
if [[ -n "$gpu_line" ]]; then
|
||||
# Extract model: everything after the colon, clean up
|
||||
GPU_MODEL=$(echo "$gpu_line" | sed 's/.*: //' | sed 's/ (rev .*)$//' | cut -c1-64)
|
||||
|
||||
# Detect vendor and passthrough type
|
||||
if echo "$gpu_line" | grep -qi "Intel"; then
|
||||
GPU_VENDOR="intel"
|
||||
GPU_PASSTHROUGH="igpu"
|
||||
elif echo "$gpu_line" | grep -qi "AMD\|ATI"; then
|
||||
GPU_VENDOR="amd"
|
||||
if echo "$gpu_line" | grep -qi "Radeon RX\|Radeon Pro"; then
|
||||
GPU_PASSTHROUGH="dgpu"
|
||||
else
|
||||
GPU_PASSTHROUGH="igpu"
|
||||
fi
|
||||
elif echo "$gpu_line" | grep -qi "NVIDIA"; then
|
||||
GPU_VENDOR="nvidia"
|
||||
GPU_PASSTHROUGH="dgpu"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Detect NVIDIA GPU
|
||||
if lspci 2>/dev/null | grep -qi "VGA.*NVIDIA\|3D.*NVIDIA"; then
|
||||
GPU_VENDOR="nvidia"
|
||||
GPU_PASSTHROUGH="dgpu"
|
||||
export GPU_VENDOR GPU_MODEL GPU_PASSTHROUGH
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# detect_cpu()
|
||||
#
|
||||
# - Detects CPU vendor and model
|
||||
# - Sets CPU_VENDOR (intel/amd/arm/unknown) and CPU_MODEL globals
|
||||
# - Used for CPU analytics
|
||||
# ------------------------------------------------------------------------------
|
||||
detect_cpu() {
|
||||
CPU_VENDOR="unknown"
|
||||
CPU_MODEL=""
|
||||
|
||||
if [[ -f /proc/cpuinfo ]]; then
|
||||
local vendor_id
|
||||
vendor_id=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ')
|
||||
|
||||
case "$vendor_id" in
|
||||
GenuineIntel) CPU_VENDOR="intel" ;;
|
||||
AuthenticAMD) CPU_VENDOR="amd" ;;
|
||||
*)
|
||||
# ARM doesn't have vendor_id, check for CPU implementer
|
||||
if grep -qi "CPU implementer" /proc/cpuinfo 2>/dev/null; then
|
||||
CPU_VENDOR="arm"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Extract model name and clean it up
|
||||
CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/ */ /g' | cut -c1-64)
|
||||
fi
|
||||
|
||||
export GPU_VENDOR GPU_PASSTHROUGH
|
||||
export CPU_VENDOR CPU_MODEL
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# detect_ram()
|
||||
#
|
||||
# - Detects RAM speed using dmidecode
|
||||
# - Sets RAM_SPEED global (e.g., "4800" for DDR5-4800)
|
||||
# - Requires root access for dmidecode
|
||||
# - Returns empty if not available
|
||||
# ------------------------------------------------------------------------------
|
||||
detect_ram() {
|
||||
RAM_SPEED=""
|
||||
|
||||
if command -v dmidecode &>/dev/null; then
|
||||
# Get configured memory speed (actual running speed)
|
||||
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Configured Memory Speed:" | grep -oE "[0-9]+" | head -1)
|
||||
|
||||
# Fallback to Speed: if Configured not available
|
||||
if [[ -z "$RAM_SPEED" ]]; then
|
||||
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Speed:" | grep -oE "[0-9]+" | head -1)
|
||||
fi
|
||||
fi
|
||||
|
||||
export RAM_SPEED
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -256,8 +315,22 @@ post_to_api() {
|
||||
if [[ -z "${GPU_VENDOR:-}" ]]; then
|
||||
detect_gpu
|
||||
fi
|
||||
local gpu_vendor="${GPU_VENDOR:-}"
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-none}"
|
||||
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
||||
local gpu_model="${GPU_MODEL:-}"
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
||||
|
||||
# Detect CPU if not already set
|
||||
if [[ -z "${CPU_VENDOR:-}" ]]; then
|
||||
detect_cpu
|
||||
fi
|
||||
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
||||
local cpu_model="${CPU_MODEL:-}"
|
||||
|
||||
# Detect RAM if not already set
|
||||
if [[ -z "${RAM_SPEED:-}" ]]; then
|
||||
detect_ram
|
||||
fi
|
||||
local ram_speed="${RAM_SPEED:-}"
|
||||
|
||||
local JSON_PAYLOAD
|
||||
JSON_PAYLOAD=$(
|
||||
@ -275,8 +348,12 @@ post_to_api() {
|
||||
"os_version": "${var_version:-}",
|
||||
"pve_version": "${pve_version}",
|
||||
"method": "${METHOD:-default}",
|
||||
"cpu_vendor": "${cpu_vendor}",
|
||||
"cpu_model": "${cpu_model}",
|
||||
"gpu_vendor": "${gpu_vendor}",
|
||||
"gpu_passthrough": "${gpu_passthrough}"
|
||||
"gpu_model": "${gpu_model}",
|
||||
"gpu_passthrough": "${gpu_passthrough}",
|
||||
"ram_speed": "${ram_speed}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
@ -384,8 +461,16 @@ post_update_to_api() {
|
||||
local exit_code=0 error="" pb_status error_category=""
|
||||
|
||||
# Get GPU info (if detected)
|
||||
local gpu_vendor="${GPU_VENDOR:-}"
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-}"
|
||||
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
||||
local gpu_model="${GPU_MODEL:-}"
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
||||
|
||||
# Get CPU info (if detected)
|
||||
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
||||
local cpu_model="${CPU_MODEL:-}"
|
||||
|
||||
# Get RAM info (if detected)
|
||||
local ram_speed="${RAM_SPEED:-}"
|
||||
|
||||
# Map status to telemetry values: installing, success, failed, unknown
|
||||
case "$status" in
|
||||
@ -449,8 +534,12 @@ post_update_to_api() {
|
||||
"error": "${error}",
|
||||
"error_category": "${error_category}",
|
||||
"install_duration": ${duration},
|
||||
"cpu_vendor": "${cpu_vendor}",
|
||||
"cpu_model": "${cpu_model}",
|
||||
"gpu_vendor": "${gpu_vendor}",
|
||||
"gpu_passthrough": "${gpu_passthrough}"
|
||||
"gpu_model": "${gpu_model}",
|
||||
"gpu_passthrough": "${gpu_passthrough}",
|
||||
"ram_speed": "${ram_speed}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
@ -86,8 +86,16 @@ type TelemetryIn struct {
|
||||
|
||||
// GPU Passthrough stats
|
||||
GPUVendor string `json:"gpu_vendor,omitempty"` // "intel", "amd", "nvidia"
|
||||
GPUModel string `json:"gpu_model,omitempty"` // e.g., "Intel Arc Graphics"
|
||||
GPUPassthrough string `json:"gpu_passthrough,omitempty"` // "igpu", "dgpu", "vgpu", "none"
|
||||
|
||||
// CPU stats
|
||||
CPUVendor string `json:"cpu_vendor,omitempty"` // "intel", "amd", "arm"
|
||||
CPUModel string `json:"cpu_model,omitempty"` // e.g., "Intel Core Ultra 7 155H"
|
||||
|
||||
// RAM stats
|
||||
RAMSpeed string `json:"ram_speed,omitempty"` // e.g., "4800" (MT/s)
|
||||
|
||||
// Performance metrics
|
||||
InstallDuration int `json:"install_duration,omitempty"` // Seconds
|
||||
|
||||
@ -114,7 +122,11 @@ type TelemetryOut struct {
|
||||
|
||||
// Extended fields
|
||||
GPUVendor string `json:"gpu_vendor,omitempty"`
|
||||
GPUModel string `json:"gpu_model,omitempty"`
|
||||
GPUPassthrough string `json:"gpu_passthrough,omitempty"`
|
||||
CPUVendor string `json:"cpu_vendor,omitempty"`
|
||||
CPUModel string `json:"cpu_model,omitempty"`
|
||||
RAMSpeed string `json:"ram_speed,omitempty"`
|
||||
InstallDuration int `json:"install_duration,omitempty"`
|
||||
ErrorCategory string `json:"error_category,omitempty"`
|
||||
}
|
||||
@ -127,7 +139,11 @@ type TelemetryStatusUpdate struct {
|
||||
InstallDuration int `json:"install_duration,omitempty"`
|
||||
ErrorCategory string `json:"error_category,omitempty"`
|
||||
GPUVendor string `json:"gpu_vendor,omitempty"`
|
||||
GPUModel string `json:"gpu_model,omitempty"`
|
||||
GPUPassthrough string `json:"gpu_passthrough,omitempty"`
|
||||
CPUVendor string `json:"cpu_vendor,omitempty"`
|
||||
CPUModel string `json:"cpu_model,omitempty"`
|
||||
RAMSpeed string `json:"ram_speed,omitempty"`
|
||||
}
|
||||
|
||||
type PBClient struct {
|
||||
@ -380,7 +396,11 @@ func (p *PBClient) UpsertTelemetry(ctx context.Context, payload TelemetryOut) er
|
||||
InstallDuration: payload.InstallDuration,
|
||||
ErrorCategory: payload.ErrorCategory,
|
||||
GPUVendor: payload.GPUVendor,
|
||||
GPUModel: payload.GPUModel,
|
||||
GPUPassthrough: payload.GPUPassthrough,
|
||||
CPUVendor: payload.CPUVendor,
|
||||
CPUModel: payload.CPUModel,
|
||||
RAMSpeed: payload.RAMSpeed,
|
||||
}
|
||||
return p.UpdateTelemetryStatus(ctx, recordID, update)
|
||||
}
|
||||
@ -548,10 +568,13 @@ var (
|
||||
}
|
||||
|
||||
// Allowed values for 'gpu_vendor' field
|
||||
allowedGPUVendor = map[string]bool{"intel": true, "amd": true, "nvidia": true, "": true}
|
||||
allowedGPUVendor = map[string]bool{"intel": true, "amd": true, "nvidia": true, "unknown": true, "": true}
|
||||
|
||||
// Allowed values for 'gpu_passthrough' field
|
||||
allowedGPUPassthrough = map[string]bool{"igpu": true, "dgpu": true, "vgpu": true, "none": true, "": true}
|
||||
allowedGPUPassthrough = map[string]bool{"igpu": true, "dgpu": true, "vgpu": true, "none": true, "unknown": true, "": true}
|
||||
|
||||
// Allowed values for 'cpu_vendor' field
|
||||
allowedCPUVendor = map[string]bool{"intel": true, "amd": true, "arm": true, "apple": true, "qualcomm": true, "unknown": true, "": true}
|
||||
|
||||
// Allowed values for 'error_category' field
|
||||
allowedErrorCategory = map[string]bool{
|
||||
@ -587,9 +610,24 @@ func validate(in *TelemetryIn) error {
|
||||
|
||||
// Sanitize extended fields
|
||||
in.GPUVendor = strings.ToLower(sanitizeShort(in.GPUVendor, 16))
|
||||
in.GPUModel = sanitizeShort(in.GPUModel, 64)
|
||||
in.GPUPassthrough = strings.ToLower(sanitizeShort(in.GPUPassthrough, 16))
|
||||
in.CPUVendor = strings.ToLower(sanitizeShort(in.CPUVendor, 16))
|
||||
in.CPUModel = sanitizeShort(in.CPUModel, 64)
|
||||
in.RAMSpeed = sanitizeShort(in.RAMSpeed, 16)
|
||||
in.ErrorCategory = strings.ToLower(sanitizeShort(in.ErrorCategory, 32))
|
||||
|
||||
// Default empty values to "unknown" for consistency
|
||||
if in.GPUVendor == "" {
|
||||
in.GPUVendor = "unknown"
|
||||
}
|
||||
if in.GPUPassthrough == "" {
|
||||
in.GPUPassthrough = "unknown"
|
||||
}
|
||||
if in.CPUVendor == "" {
|
||||
in.CPUVendor = "unknown"
|
||||
}
|
||||
|
||||
// IMPORTANT: "error" must be short and not contain identifiers/logs
|
||||
in.Error = sanitizeShort(in.Error, 120)
|
||||
|
||||
@ -613,10 +651,13 @@ func validate(in *TelemetryIn) error {
|
||||
|
||||
// Validate new enum fields
|
||||
if !allowedGPUVendor[in.GPUVendor] {
|
||||
return errors.New("invalid gpu_vendor (must be 'intel', 'amd', 'nvidia', or empty)")
|
||||
return errors.New("invalid gpu_vendor (must be 'intel', 'amd', 'nvidia', 'unknown')")
|
||||
}
|
||||
if !allowedGPUPassthrough[in.GPUPassthrough] {
|
||||
return errors.New("invalid gpu_passthrough (must be 'igpu', 'dgpu', 'vgpu', 'none', or empty)")
|
||||
return errors.New("invalid gpu_passthrough (must be 'igpu', 'dgpu', 'vgpu', 'none', 'unknown')")
|
||||
}
|
||||
if !allowedCPUVendor[in.CPUVendor] {
|
||||
return errors.New("invalid cpu_vendor (must be 'intel', 'amd', 'arm', 'apple', 'qualcomm', 'unknown')")
|
||||
}
|
||||
if !allowedErrorCategory[in.ErrorCategory] {
|
||||
return errors.New("invalid error_category")
|
||||
@ -993,7 +1034,11 @@ func main() {
|
||||
Error: in.Error,
|
||||
ExitCode: in.ExitCode,
|
||||
GPUVendor: in.GPUVendor,
|
||||
GPUModel: in.GPUModel,
|
||||
GPUPassthrough: in.GPUPassthrough,
|
||||
CPUVendor: in.CPUVendor,
|
||||
CPUModel: in.CPUModel,
|
||||
RAMSpeed: in.RAMSpeed,
|
||||
InstallDuration: in.InstallDuration,
|
||||
ErrorCategory: in.ErrorCategory,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user