diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php
index cde8d4ed2..97a8f3e2f 100644
--- a/web/skins/classic/includes/functions.php
+++ b/web/skins/classic/includes/functions.php
@@ -210,6 +210,7 @@ function buildSidebarMenu() {
getOptionsHTML($forLeftBar = true) .
getLogHTML($forLeftBar = true) .
getDevicesHTML($forLeftBar = true) .
+ getIntelGpuHTML($forLeftBar = true) .
getGroupsHTML($view, $forLeftBar = true) .
getFilterHTML($view, $forLeftBar = true) .
getSnapshotsHTML($view, $forLeftBar = true) .
@@ -511,6 +512,7 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
echo getOptionsHTML();
echo getLogHTML();
echo getDevicesHTML();
+ echo getIntelGpuHTML();
echo getGroupsHTML($view);
echo getFilterHTML($view);
echo getCycleHTML($view);
@@ -668,6 +670,7 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
echo getOptionsHTML();
echo getLogHTML();
echo getDevicesHTML();
+ echo getIntelGpuHTML();
echo getGroupsHTML($view);
echo getFilterHTML($view);
echo getCycleHTML($view);
@@ -1132,6 +1135,34 @@ function getDevicesHTML($forLeftBar = false) {
return $result;
}
+// Returns the html representing the Intel GPU status menu item
+function getIntelGpuHTML($forLeftBar = false) {
+ $result = '';
+
+ // Only show if intel_gpu_top is available and user can view System
+ if (canView('System')) {
+ // Check if intel_gpu_top command exists
+ $intel_gpu_top_exists = shell_exec('which intel_gpu_top 2>/dev/null');
+ if ($intel_gpu_top_exists) {
+ if ($forLeftBar) {
+ $result .= buildMenuItem(
+ $viewItemName = 'intelgpu',
+ $id = 'getIntelGpuHTML',
+ $itemName = 'Intel GPU',
+ $href = '?view=intelgpu',
+ $icon = 'memory',
+ $classNameForTag_A = '',
+ $subMenu = ''
+ );
+ } else {
+ $result .= '
Intel GPU'.PHP_EOL;
+ }
+ }
+ }
+
+ return $result;
+}
+
// Returns the html representing the Groups menu item
function getGroupsHTML($view, $forLeftBar = false) {
$result = '';
diff --git a/web/skins/classic/views/intelgpu.php b/web/skins/classic/views/intelgpu.php
new file mode 100644
index 000000000..186fe5fb1
--- /dev/null
+++ b/web/skins/classic/views/intelgpu.php
@@ -0,0 +1,468 @@
+&1', $output, $returnCode);
+
+ $jsonOutput = implode("\n", $output);
+
+ // Check for permission error
+ if (strpos($jsonOutput, 'Permission denied') !== false || strpos($jsonOutput, 'CAP_PERFMON') !== false) {
+ return ['error' => 'Permission denied. intel_gpu_top requires CAP_PERFMON capability or root access. See "man 7 capabilities" for details.'];
+ }
+
+ if ($returnCode != 0 && empty($output)) {
+ return ['error' => 'Failed to run intel_gpu_top. Is it installed? (apt install intel-gpu-tools)'];
+ }
+
+ // intel_gpu_top outputs multiple JSON objects (one per sample), we want the last complete one
+ // Find the last complete JSON object
+ $jsonObjects = [];
+ $depth = 0;
+ $start = -1;
+
+ for ($i = 0; $i < strlen($jsonOutput); $i++) {
+ $char = $jsonOutput[$i];
+ if ($char === '{') {
+ if ($depth === 0) {
+ $start = $i;
+ }
+ $depth++;
+ } else if ($char === '}') {
+ $depth--;
+ if ($depth === 0 && $start >= 0) {
+ $jsonObjects[] = substr($jsonOutput, $start, $i - $start + 1);
+ $start = -1;
+ }
+ }
+ }
+
+ if (empty($jsonObjects)) {
+ return ['error' => 'No valid JSON output from intel_gpu_top. Raw output: ' . substr($jsonOutput, 0, 500)];
+ }
+
+ // Use the last complete JSON object
+ $lastJson = end($jsonObjects);
+ $data = json_decode($lastJson, true);
+
+ if ($data === null) {
+ return ['error' => 'Failed to parse JSON from intel_gpu_top: ' . json_last_error_msg()];
+ }
+
+ return $data;
+}
+
+// Get list of Intel GPU devices
+function getIntelGpuDevices() {
+ $output = [];
+ $returnCode = 0;
+
+ exec('intel_gpu_top -L 2>&1', $output, $returnCode);
+
+ if ($returnCode != 0 || empty($output)) {
+ return [];
+ }
+
+ $devices = [];
+ foreach ($output as $line) {
+ $line = trim($line);
+ if (!empty($line) && strpos($line, 'card') !== false) {
+ $devices[] = $line;
+ }
+ }
+
+ return $devices;
+}
+
+// Get additional GPU info from sysfs/lspci
+function getIntelGpuInfo() {
+ $info = [
+ 'model' => 'Unknown',
+ 'driver' => 'Unknown',
+ 'pci_id' => 'Unknown',
+ ];
+
+ // Try to get GPU model from lspci
+ $output = [];
+ exec('lspci -nn 2>/dev/null | grep -i "VGA\|Display\|3D" | grep -i intel', $output);
+ if (!empty($output)) {
+ $info['model'] = trim($output[0]);
+ if (preg_match('/\[([0-9a-f]{4}:[0-9a-f]{4})\]/i', $output[0], $matches)) {
+ $info['pci_id'] = $matches[1];
+ }
+ }
+
+ // Try to get driver version
+ $output = [];
+ exec('cat /sys/module/i915/version 2>/dev/null', $output);
+ if (!empty($output)) {
+ $info['driver'] = trim($output[0]);
+ } else {
+ // Try modinfo
+ $output = [];
+ exec('modinfo i915 2>/dev/null | grep "^version:"', $output);
+ if (!empty($output)) {
+ $info['driver'] = trim(str_replace('version:', '', $output[0]));
+ }
+ }
+
+ return $info;
+}
+
+// Format percentage with color coding
+function formatPercentage($value, $inverse = false) {
+ $value = floatval($value);
+ if ($inverse) {
+ // For RC6, higher is better (GPU is idle)
+ if ($value >= 80) {
+ $class = 'text-success';
+ } else if ($value >= 50) {
+ $class = 'text-warning';
+ } else {
+ $class = 'text-danger';
+ }
+ } else {
+ // For usage, lower is better (less loaded)
+ if ($value >= 80) {
+ $class = 'text-danger';
+ } else if ($value >= 50) {
+ $class = 'text-warning';
+ } else {
+ $class = 'text-success';
+ }
+ }
+ return '' . number_format($value, 1) . '%';
+}
+
+$gpuData = parseIntelGpuTop();
+$gpuDevices = getIntelGpuDevices();
+$gpuInfo = getIntelGpuInfo();
+
+xhtmlHeaders(__FILE__, 'Intel GPU Status');
+getBodyTopHTML();
+echo getNavBarHTML();
+?>
+
+
memory Intel GPU Status
+
+
+
+
+
+
+
+
+
+ Error:
+
+
+
+
+
+
+
+
+
+ Device:
+
+
+ Driver:
+
+
+ PCI ID:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Requested Frequency: |
+ |
+
+
+ | Actual Frequency: |
+ |
+
+
+
+
+
+ | GPU Power: |
+ |
+
+
+
+
+ | Package Power: |
+ |
+
+
+
+
+
+ | RC6 (Idle): |
+ |
+
+
+
+
+ | Interrupts: |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Reads: |
+ |
+
+
+ | Writes: |
+ |
+
+
+ | Total: |
+ |
+
+
+
+
IMC bandwidth data not available
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Engine |
+ Busy |
+ Semaphore Wait |
+ Memory Wait |
+ Utilization Bar |
+
+
+
+ $engineData):
+ if (!is_array($engineData)) continue;
+ $busy = floatval($engineData['busy'] ?? 0);
+ $sema = floatval($engineData['sema'] ?? 0);
+ $wait = floatval($engineData['wait'] ?? 0);
+ $totalBusy += $busy;
+ $engineCount++;
+ ?>
+
+ |
+ |
+ % |
+ % |
+
+
+
+ = 10): ?>%
+
+ 0): ?>
+
+
+ 0): ?>
+
+
+
+ |
+
+
+
+ 0): ?>
+
+
+ | Average |
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | PID |
+ Name |
+
+ |
+
+
+
+
+
+
+ |
+ |
+
+
+ % |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Engines
+
+ - Render/3D: 3D rendering workloads
+ - Blitter: 2D copy operations
+ - Video: Hardware video decode (VAAPI)
+ - VideoEnhance: Video post-processing
+
+
+
+
Utilization
+
+ - Busy: Engine actively processing
+ - Sema: Waiting on semaphore
+ - Wait: Waiting on memory
+
+
+
+
Power States
+
+ - RC6: Render C-state 6 (deep idle)
+ - Higher RC6 = More power efficient
+ - IMC: Integrated Memory Controller
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/skins/classic/views/js/intelgpu.js b/web/skins/classic/views/js/intelgpu.js
new file mode 100644
index 000000000..d4e59da0c
--- /dev/null
+++ b/web/skins/classic/views/js/intelgpu.js
@@ -0,0 +1,18 @@
+function initPage() {
+ // Manage the BACK button
+ document.getElementById("backBtn").addEventListener("click", function onBackClick(evt) {
+ evt.preventDefault();
+ window.history.back();
+ });
+
+ // Disable the back button if there is nothing to go back to
+ $j('#backBtn').prop('disabled', !document.referrer.length);
+
+ // Manage the REFRESH Button
+ document.getElementById("refreshBtn").addEventListener("click", function onRefreshClick(evt) {
+ evt.preventDefault();
+ window.location.reload(true);
+ });
+}
+
+$j(document).ready(initPage);