Add GPU PCI ID fallback names and container passthrough indicators
This commit is contained in:
Binary file not shown.
67
app/app.py
67
app/app.py
@@ -738,6 +738,19 @@ def temp_color(temp_c):
|
|||||||
return '#22c55e'
|
return '#22c55e'
|
||||||
|
|
||||||
|
|
||||||
|
# Fallback lookup for GPUs with vague lspci names (outdated pciid database)
|
||||||
|
_GPU_PCI_FALLBACK = {
|
||||||
|
# Intel Arc Battlemage
|
||||||
|
'e20b': 'Intel Arc B580',
|
||||||
|
'e20c': 'Intel Arc B570',
|
||||||
|
# Intel Arc Alchemist
|
||||||
|
'56a0': 'Intel Arc A770',
|
||||||
|
'56a1': 'Intel Arc A770',
|
||||||
|
'56a5': 'Intel Arc A580',
|
||||||
|
'56a6': 'Intel Arc A380',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('clean_gpu')
|
@app.template_filter('clean_gpu')
|
||||||
def clean_gpu(description):
|
def clean_gpu(description):
|
||||||
if not description:
|
if not description:
|
||||||
@@ -745,6 +758,14 @@ def clean_gpu(description):
|
|||||||
s = str(description)
|
s = str(description)
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
# Extract PCI device ID from lspci -nn output (e.g. [8086:e20b])
|
||||||
|
pci_id_match = re.search(r'\[([0-9a-f]{4}):([0-9a-f]{4})\]', s, flags=re.IGNORECASE)
|
||||||
|
pci_device_id = pci_id_match.group(2).lower() if pci_id_match else None
|
||||||
|
|
||||||
|
# Remove PCI class and device ID brackets
|
||||||
|
s = re.sub(r'\s*\[[0-9a-f]{4}:[0-9a-f]{4}\]', '', s, flags=re.IGNORECASE)
|
||||||
|
s = re.sub(r'\s*\[[0-9a-f]{4}\]', '', s, flags=re.IGNORECASE)
|
||||||
|
|
||||||
# Strip PCI address prefix (e.g. "01:00.0 ")
|
# Strip PCI address prefix (e.g. "01:00.0 ")
|
||||||
s = re.sub(r'^[0-9a-f:.]+\s+', '', s, flags=re.IGNORECASE)
|
s = re.sub(r'^[0-9a-f:.]+\s+', '', s, flags=re.IGNORECASE)
|
||||||
# Strip type prefix
|
# Strip type prefix
|
||||||
@@ -786,6 +807,14 @@ def clean_gpu(description):
|
|||||||
# Remove trailing whitespace
|
# Remove trailing whitespace
|
||||||
s = s.strip()
|
s = s.strip()
|
||||||
|
|
||||||
|
# If the name is too vague, try PCI device ID fallback
|
||||||
|
if pci_device_id:
|
||||||
|
vague = not s or s.lower() in ('graphics',) or s.lower().startswith('device ')
|
||||||
|
if vague:
|
||||||
|
fallback = _GPU_PCI_FALLBACK.get(pci_device_id)
|
||||||
|
if fallback:
|
||||||
|
return fallback
|
||||||
|
|
||||||
# Don't duplicate manufacturer if already in the model name
|
# Don't duplicate manufacturer if already in the model name
|
||||||
if manufacturer and s:
|
if manufacturer and s:
|
||||||
s_check = s.lower()
|
s_check = s.lower()
|
||||||
@@ -795,6 +824,44 @@ def clean_gpu(description):
|
|||||||
return s or '-'
|
return s or '-'
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_pci(addr):
|
||||||
|
"""Normalize PCI address to bus:device for matching (strip domain and function)."""
|
||||||
|
if not addr:
|
||||||
|
return ''
|
||||||
|
addr = str(addr).strip()
|
||||||
|
# Strip domain prefix (0000:)
|
||||||
|
if addr.count(':') == 2:
|
||||||
|
addr = addr.split(':', 1)[1]
|
||||||
|
# Strip function suffix (.0)
|
||||||
|
return addr.split('.')[0].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@app.template_filter('gpu_passthrough_map')
|
||||||
|
def gpu_passthrough_map(details):
|
||||||
|
"""Build mapping of normalized PCI address -> container name for GPU passthrough."""
|
||||||
|
if not details:
|
||||||
|
return {}
|
||||||
|
mapping = {}
|
||||||
|
containers = details.get('container', [])
|
||||||
|
if not containers:
|
||||||
|
return {}
|
||||||
|
for c in containers:
|
||||||
|
pt = c.get('gpu_passthrough', '')
|
||||||
|
if not pt:
|
||||||
|
continue
|
||||||
|
name = c.get('name', c.get('_name', '?'))
|
||||||
|
for pci_addr in pt.split(','):
|
||||||
|
normalized = _normalize_pci(pci_addr)
|
||||||
|
if normalized:
|
||||||
|
mapping.setdefault(normalized, []).append(name)
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
@app.template_filter('normalize_pci')
|
||||||
|
def normalize_pci_filter(addr):
|
||||||
|
return _normalize_pci(addr)
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('usage_color')
|
@app.template_filter('usage_color')
|
||||||
def usage_color(percent):
|
def usage_color(percent):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -135,13 +135,15 @@ df -B1 --output=target,size,used,avail,pcent \
|
|||||||
echo "usage_percent=${percent%\%}"
|
echo "usage_percent=${percent%\%}"
|
||||||
done
|
done
|
||||||
|
|
||||||
# GPUs
|
# GPUs (use -nn for numeric PCI IDs as fallback for outdated pciid databases)
|
||||||
gpu_idx=0
|
gpu_idx=0
|
||||||
while read -r line; do
|
while read -r line; do
|
||||||
echo "[gpu:$gpu_idx]"
|
echo "[gpu:$gpu_idx]"
|
||||||
echo "description=$line"
|
echo "description=$line"
|
||||||
|
pci_addr=$(echo "$line" | awk '{print $1}')
|
||||||
|
echo "pci_address=$pci_addr"
|
||||||
gpu_idx=$((gpu_idx + 1))
|
gpu_idx=$((gpu_idx + 1))
|
||||||
done < <(lspci 2>/dev/null | grep -iE 'vga|3d|display')
|
done < <(lspci -nn 2>/dev/null | grep -iE 'vga|3d|display')
|
||||||
|
|
||||||
# GPU utilization (NVIDIA)
|
# GPU utilization (NVIDIA)
|
||||||
if command -v nvidia-smi &>/dev/null; then
|
if command -v nvidia-smi &>/dev/null; then
|
||||||
@@ -171,6 +173,10 @@ if [ -n "$igpu_cmd" ]; then
|
|||||||
igpu_raw=$(timeout 2 $igpu_prefix intel_gpu_top -J -s 500 -d /dev/dri/"$card" 2>/dev/null)
|
igpu_raw=$(timeout 2 $igpu_prefix intel_gpu_top -J -s 500 -d /dev/dri/"$card" 2>/dev/null)
|
||||||
if [ -n "$igpu_raw" ]; then
|
if [ -n "$igpu_raw" ]; then
|
||||||
echo "[intel_gpu:$igpu_idx]"
|
echo "[intel_gpu:$igpu_idx]"
|
||||||
|
pci_addr=$(basename "$(readlink "$drm/device" 2>/dev/null)" 2>/dev/null)
|
||||||
|
[ -n "$pci_addr" ] && echo "pci_address=$pci_addr"
|
||||||
|
gpu_desc=$(lspci -nn -s "$pci_addr" 2>/dev/null)
|
||||||
|
[ -n "$gpu_desc" ] && echo "name=$gpu_desc"
|
||||||
busy=$(echo "$igpu_raw" | grep -oP '"busy"\s*:\s*\K[0-9.]+' | sort -rn | head -1)
|
busy=$(echo "$igpu_raw" | grep -oP '"busy"\s*:\s*\K[0-9.]+' | sort -rn | head -1)
|
||||||
echo "utilization_percent=${busy:-0}"
|
echo "utilization_percent=${busy:-0}"
|
||||||
freq=$(echo "$igpu_raw" | grep -oP '"actual"\s*:\s*\K[0-9.]+' | head -1)
|
freq=$(echo "$igpu_raw" | grep -oP '"actual"\s*:\s*\K[0-9.]+' | head -1)
|
||||||
@@ -292,6 +298,31 @@ if command -v pct &>/dev/null; then
|
|||||||
if [ "$status" = "running" ]; then
|
if [ "$status" = "running" ]; then
|
||||||
gather_container_stats "_sudo pct exec $vmid --"
|
gather_container_stats "_sudo pct exec $vmid --"
|
||||||
fi
|
fi
|
||||||
|
# Check for GPU/device passthrough in LXC config
|
||||||
|
if [ -f "/etc/pve/lxc/${vmid}.conf" ]; then
|
||||||
|
pt_addrs=""
|
||||||
|
while read -r dev_line; do
|
||||||
|
dev_path=$(echo "$dev_line" | sed 's/dev[0-9]*: *//' | cut -d, -f1)
|
||||||
|
case "$dev_path" in
|
||||||
|
/dev/dri/card*)
|
||||||
|
card_name=$(basename "$dev_path")
|
||||||
|
pci=$(basename "$(readlink "/sys/class/drm/$card_name/device" 2>/dev/null)" 2>/dev/null)
|
||||||
|
if [ -n "$pci" ] && ! echo "$pt_addrs" | grep -q "$pci"; then
|
||||||
|
pt_addrs="${pt_addrs:+$pt_addrs,}$pci"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
/dev/dri/renderD*)
|
||||||
|
render_minor=$(basename "$dev_path" | grep -oE '[0-9]+')
|
||||||
|
card_num=$((render_minor - 128))
|
||||||
|
pci=$(basename "$(readlink "/sys/class/drm/card${card_num}/device" 2>/dev/null)" 2>/dev/null)
|
||||||
|
if [ -n "$pci" ] && ! echo "$pt_addrs" | grep -q "$pci"; then
|
||||||
|
pt_addrs="${pt_addrs:+$pt_addrs,}$pci"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < <(grep -E '^dev[0-9]+:' "/etc/pve/lxc/${vmid}.conf" 2>/dev/null)
|
||||||
|
[ -n "$pt_addrs" ] && echo "gpu_passthrough=$pt_addrs"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -313,6 +344,13 @@ if command -v qm &>/dev/null; then
|
|||||||
gather_container_stats "_sudo qm guest exec $vmid --"
|
gather_container_stats "_sudo qm guest exec $vmid --"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# Get PCI passthrough devices from VM config
|
||||||
|
pt_addrs=""
|
||||||
|
while read -r pci_line; do
|
||||||
|
pci_dev=$(echo "$pci_line" | sed 's/hostpci[0-9]*: *//' | cut -d, -f1)
|
||||||
|
[ -n "$pci_dev" ] && pt_addrs="${pt_addrs:+$pt_addrs,}$pci_dev"
|
||||||
|
done < <(_sudo qm config "$vmid" 2>/dev/null | grep -E '^hostpci[0-9]+:')
|
||||||
|
[ -n "$pt_addrs" ] && echo "gpu_passthrough=$pt_addrs"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -356,6 +356,12 @@ main {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gpu-passthrough {
|
||||||
|
color: #38bdf8;
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.table-header td {
|
.table-header td {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #94a3b8 !important;
|
color: #94a3b8 !important;
|
||||||
|
|||||||
@@ -329,20 +329,24 @@
|
|||||||
<!-- GPUs -->
|
<!-- GPUs -->
|
||||||
{% set gpus = d.get('gpu', []) if d.get('gpu') else [] %}
|
{% set gpus = d.get('gpu', []) if d.get('gpu') else [] %}
|
||||||
{% if gpus or nvidia_gpus or intel_gpus %}
|
{% if gpus or nvidia_gpus or intel_gpus %}
|
||||||
|
{% set gpu_pt = d|gpu_passthrough_map %}
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<h4>GPUs</h4>
|
<h4>GPUs</h4>
|
||||||
<table>
|
<table>
|
||||||
{% for ng in nvidia_gpus %}
|
{% for ng in nvidia_gpus %}
|
||||||
|
{% set pt_users = gpu_pt.get(ng.get('pci_address', '')|normalize_pci) %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ ng.get('name', 'NVIDIA GPU ' ~ loop.index0) }}</td>
|
<td>{{ ng.get('name', 'NVIDIA GPU ' ~ loop.index0) }}{% if pt_users %} <span class="gpu-passthrough">→ {{ pt_users|join(', ') }}</span>{% endif %}</td>
|
||||||
<td>{{ ng.get('utilization_percent', '-') }}%</td>
|
<td>{{ ng.get('utilization_percent', '-') }}%</td>
|
||||||
<td>{{ ng.get('memory_used_mb', '-') }} / {{ ng.get('memory_total_mb', '-') }} MB</td>
|
<td>{{ ng.get('memory_used_mb', '-') }} / {{ ng.get('memory_total_mb', '-') }} MB</td>
|
||||||
<td>{{ ng.get('temperature', '-') }}°C</td>
|
<td>{{ ng.get('temperature', '-') }}°C</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for ig in intel_gpus %}
|
{% for ig in intel_gpus %}
|
||||||
|
{% set ig_name = ig.get('name', '')|clean_gpu if ig.get('name') else 'Intel GPU ' ~ loop.index0 %}
|
||||||
|
{% set pt_users = gpu_pt.get(ig.get('pci_address', '')|normalize_pci) %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Intel GPU {{ loop.index0 }}</td>
|
<td>{{ ig_name }}{% if pt_users %} <span class="gpu-passthrough">→ {{ pt_users|join(', ') }}</span>{% endif %}</td>
|
||||||
<td>{{ ig.get('utilization_percent', '-') }}%</td>
|
<td>{{ ig.get('utilization_percent', '-') }}%</td>
|
||||||
<td>{% if ig.get('frequency_mhz') %}{{ ig.get('frequency_mhz') }} MHz{% else %}-{% endif %}</td>
|
<td>{% if ig.get('frequency_mhz') %}{{ ig.get('frequency_mhz') }} MHz{% else %}-{% endif %}</td>
|
||||||
<td>{% if ig.get('power_w') %}{{ ig.get('power_w') }} W{% else %}-{% endif %}</td>
|
<td>{% if ig.get('power_w') %}{{ ig.get('power_w') }} W{% else %}-{% endif %}</td>
|
||||||
@@ -350,7 +354,8 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if not nvidia_gpus and not intel_gpus %}
|
{% if not nvidia_gpus and not intel_gpus %}
|
||||||
{% for gpu in gpus %}
|
{% for gpu in gpus %}
|
||||||
<tr><td colspan="4">{{ gpu.get('description', '-')|clean_gpu }}</td></tr>
|
{% set pt_users = gpu_pt.get(gpu.get('pci_address', '')|normalize_pci) %}
|
||||||
|
<tr><td colspan="4">{{ gpu.get('description', '-')|clean_gpu }}{% if pt_users %} <span class="gpu-passthrough">→ {{ pt_users|join(', ') }}</span>{% endif %}</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
Reference in New Issue
Block a user