Skip to content

Commit 09af16b

Browse files
fix: support multiple Quadra cards and fix phantom card listing
Merge duplicate JSON section keys from ni_rsrc_mon output instead of overwriting, so all cards are captured. Group summary and session data per-card using INDEX from summary output. Match detailed sessions to cards by DEVICE field rather than INDEX, since INDEX in detailed output is the session number not the card index. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c00f36b commit 09af16b

File tree

1 file changed

+93
-62
lines changed

1 file changed

+93
-62
lines changed

web/skins/classic/views/quadra.php

Lines changed: 93 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ function parseQuadraJsonOutput($args) {
131131
$json = fixQuadraJson($block[0]);
132132
$parsed = json_decode($json, true);
133133
if ($parsed !== null && isset($parsed[$key])) {
134-
$result[$key] = $parsed[$key];
134+
if (!isset($result[$key])) $result[$key] = [];
135+
$result[$key] = array_merge($result[$key], $parsed[$key]);
135136
$sectionCount++;
136137
} else {
137138
$jsonError = json_last_error_msg();
@@ -167,15 +168,6 @@ function formatBitrate($bps) {
167168
$quadraSummary = parseQuadraJsonOutput('-o json');
168169
$quadraDetailed = parseQuadraJsonOutput('-d -o json');
169170

170-
// Extract device info from the first available section entry
171-
$deviceInfo = null;
172-
foreach (['decoder', 'encoder', 'scaler', 'AI', 'uploader', 'nvme'] as $sect) {
173-
if (!empty($quadraSummary[$sect][0])) {
174-
$deviceInfo = $quadraSummary[$sect][0];
175-
break;
176-
}
177-
}
178-
179171
$resourceSections = [
180172
'decoder' => ['label' => 'Decoder', 'icon' => 'input'],
181173
'encoder' => ['label' => 'Encoder', 'icon' => 'output'],
@@ -190,6 +182,53 @@ function formatBitrate($bps) {
190182
'pcie' => ['label' => 'PCIe', 'icon' => 'settings_ethernet'],
191183
];
192184

185+
// Build per-card data indexed by card INDEX
186+
// Each entry in the summary/detailed arrays has an INDEX field identifying the card
187+
$cards = [];
188+
$allSections = array_merge(array_keys($resourceSections), array_keys($infraSections));
189+
foreach ($allSections as $sect) {
190+
if (empty($quadraSummary[$sect])) continue;
191+
foreach ($quadraSummary[$sect] as $entry) {
192+
$idx = $entry['INDEX'] ?? 0;
193+
if (!isset($cards[$idx])) {
194+
$cards[$idx] = [
195+
'device' => $entry['DEVICE'] ?? 'N/A',
196+
'pcie_addr' => $entry['PCIE_ADDR'] ?? 'N/A',
197+
'numa_node' => $entry['NUMA_NODE'] ?? '?',
198+
'firmware' => $entry['FR'] ?? ($quadraSummary['version'] ?? 'N/A'),
199+
'summary' => [],
200+
'detailed' => [],
201+
];
202+
}
203+
if (!isset($cards[$idx]['summary'][$sect])) $cards[$idx]['summary'][$sect] = [];
204+
$cards[$idx]['summary'][$sect][] = $entry;
205+
}
206+
}
207+
// Build DEVICE -> card index lookup from summary data
208+
$deviceToCard = [];
209+
foreach ($cards as $idx => $card) {
210+
$deviceToCard[$card['device']] = $idx;
211+
}
212+
// Assign detailed session data to cards by matching DEVICE field.
213+
// In detailed output INDEX is the session number, not the card index.
214+
foreach (['decoder', 'encoder'] as $sect) {
215+
if (empty($quadraDetailed[$sect])) continue;
216+
foreach ($quadraDetailed[$sect] as $entry) {
217+
$dev = $entry['DEVICE'] ?? '';
218+
if (isset($deviceToCard[$dev])) {
219+
$idx = $deviceToCard[$dev];
220+
} else {
221+
// Fall back to first card if DEVICE not found
222+
$idx = array_key_first($cards);
223+
if ($idx === null) continue;
224+
}
225+
if (!isset($cards[$idx]['detailed'][$sect])) $cards[$idx]['detailed'][$sect] = [];
226+
$cards[$idx]['detailed'][$sect][] = $entry;
227+
}
228+
}
229+
ksort($cards);
230+
$hasData = !empty($cards);
231+
193232
xhtmlHeaders(__FILE__, 'Quadra Status');
194233
getBodyTopHTML();
195234
echo getNavBarHTML();
@@ -208,55 +247,56 @@ function formatBitrate($bps) {
208247
<strong>Error running ni_rsrc_mon:</strong><br>
209248
<?php echo htmlspecialchars($quadraSummary['error']) ?>
210249
</div>
211-
<?php elseif (!$deviceInfo && empty($quadraSummary['decoder']) && empty($quadraSummary['encoder'])): ?>
250+
<?php elseif (!$hasData): ?>
212251
<div class="alert alert-warning">
213252
<strong>No data:</strong> ni_rsrc_mon returned no resource data.
214253
Check that a NetInt Quadra device is installed and that the web server user has permission to access it.
215254
</div>
216255
<?php else: ?>
217256

218-
<!-- Device Information -->
257+
<!-- System-wide info -->
219258
<div class="card mb-3">
220259
<div class="card-header">
221-
<i class="material-icons md-18">info</i> Device Information
260+
<i class="material-icons md-18">info</i> System Information
222261
</div>
223262
<div class="card-body">
224263
<div class="row">
225264
<div class="col-md-3">
226265
<strong>Timestamp:</strong> <?php echo htmlspecialchars($quadraSummary['timestamp'] ?: 'N/A') ?>
227266
</div>
228-
<div class="col-md-2">
267+
<div class="col-md-3">
229268
<strong>Uptime:</strong> <?php echo htmlspecialchars($quadraSummary['uptime'] ?: 'N/A') ?>
230269
</div>
231-
<div class="col-md-2">
232-
<strong>Firmware:</strong> v<?php echo htmlspecialchars($quadraSummary['version'] ?: 'N/A') ?>
233-
</div>
234-
<?php if ($deviceInfo): ?>
235-
<div class="col-md-2">
236-
<strong>Device:</strong> <?php echo htmlspecialchars($deviceInfo['DEVICE'] ?? 'N/A') ?>
237-
</div>
238270
<div class="col-md-3">
239-
<strong>PCIe:</strong> <?php echo htmlspecialchars($deviceInfo['PCIE_ADDR'] ?? 'N/A') ?>
240-
(NUMA <?php echo htmlspecialchars($deviceInfo['NUMA_NODE'] ?? '?') ?>)
271+
<strong>Cards detected:</strong> <?php echo count($cards) ?>
241272
</div>
242-
<?php endif; ?>
243273
</div>
244274
</div>
245275
</div>
246276

247-
<!-- Resource Utilization -->
277+
<?php foreach ($cards as $cardIdx => $card): ?>
278+
<!-- Card <?php echo $cardIdx ?> -->
279+
<div class="card mb-3">
280+
<div class="card-header">
281+
<i class="material-icons md-18">memory</i>
282+
<strong>Card <?php echo $cardIdx ?></strong>
283+
&mdash; <?php echo htmlspecialchars($card['device']) ?>
284+
@ <?php echo htmlspecialchars($card['pcie_addr']) ?>
285+
(NUMA <?php echo htmlspecialchars($card['numa_node']) ?>)
286+
&mdash; FW v<?php echo htmlspecialchars($card['firmware']) ?>
287+
</div>
288+
<div class="card-body">
289+
290+
<!-- Resource Utilization -->
248291
<?php
249292
$hasResources = false;
250293
foreach ($resourceSections as $key => $meta) {
251-
if (!empty($quadraSummary[$key])) { $hasResources = true; break; }
294+
if (!empty($card['summary'][$key])) { $hasResources = true; break; }
252295
}
253296
if ($hasResources):
254297
?>
255-
<div class="card mb-3">
256-
<div class="card-header">
257-
<i class="material-icons md-18">speed</i> Resource Utilization
258-
</div>
259-
<div class="card-body table-responsive">
298+
<h6><i class="material-icons md-18">speed</i> Resource Utilization</h6>
299+
<div class="table-responsive mb-3">
260300
<table class="table table-sm table-striped table-hover">
261301
<thead class="thead-highlight text-left">
262302
<tr>
@@ -272,8 +312,8 @@ function formatBitrate($bps) {
272312
</thead>
273313
<tbody>
274314
<?php foreach ($resourceSections as $key => $meta):
275-
if (empty($quadraSummary[$key])) continue;
276-
foreach ($quadraSummary[$key] as $entry):
315+
if (empty($card['summary'][$key])) continue;
316+
foreach ($card['summary'][$key] as $entry):
277317
?>
278318
<tr>
279319
<td><i class="material-icons md-18"><?php echo $meta['icon'] ?></i> <?php echo $meta['label'] ?></td>
@@ -289,22 +329,18 @@ function formatBitrate($bps) {
289329
</tbody>
290330
</table>
291331
</div>
292-
</div>
293332
<?php endif; ?>
294333

295-
<!-- Infrastructure -->
334+
<!-- Infrastructure -->
296335
<?php
297336
$hasInfra = false;
298337
foreach ($infraSections as $key => $meta) {
299-
if (!empty($quadraSummary[$key])) { $hasInfra = true; break; }
338+
if (!empty($card['summary'][$key])) { $hasInfra = true; break; }
300339
}
301340
if ($hasInfra):
302341
?>
303-
<div class="card mb-3">
304-
<div class="card-header">
305-
<i class="material-icons md-18">developer_board</i> Infrastructure
306-
</div>
307-
<div class="card-body table-responsive">
342+
<h6><i class="material-icons md-18">developer_board</i> Infrastructure</h6>
343+
<div class="table-responsive mb-3">
308344
<table class="table table-sm table-striped table-hover">
309345
<thead class="thead-highlight text-left">
310346
<tr>
@@ -316,8 +352,8 @@ function formatBitrate($bps) {
316352
</thead>
317353
<tbody>
318354
<?php foreach ($infraSections as $key => $meta):
319-
if (empty($quadraSummary[$key])) continue;
320-
foreach ($quadraSummary[$key] as $entry):
355+
if (empty($card['summary'][$key])) continue;
356+
foreach ($card['summary'][$key] as $entry):
321357
?>
322358
<tr>
323359
<td><i class="material-icons md-18"><?php echo $meta['icon'] ?></i> <?php echo $meta['label'] ?></td>
@@ -329,16 +365,12 @@ function formatBitrate($bps) {
329365
</tbody>
330366
</table>
331367
</div>
332-
</div>
333368
<?php endif; ?>
334369

335-
<!-- Active Decoder Sessions -->
336-
<?php if (!empty($quadraDetailed['decoder'])): ?>
337-
<div class="card mb-3">
338-
<div class="card-header">
339-
<i class="material-icons md-18">input</i> Active Decoder Sessions (<?php echo count($quadraDetailed['decoder']) ?>)
340-
</div>
341-
<div class="card-body table-responsive">
370+
<!-- Active Decoder Sessions -->
371+
<?php if (!empty($card['detailed']['decoder'])): ?>
372+
<h6><i class="material-icons md-18">input</i> Active Decoder Sessions (<?php echo count($card['detailed']['decoder']) ?>)</h6>
373+
<div class="table-responsive mb-3">
342374
<table class="table table-sm table-striped table-hover">
343375
<thead class="thead-highlight text-left">
344376
<tr>
@@ -357,7 +389,7 @@ function formatBitrate($bps) {
357389
$totalDecoderFps = 0;
358390
$totalDecoderInFrames = 0;
359391
$totalDecoderOutFrames = 0;
360-
foreach ($quadraDetailed['decoder'] as $decoder):
392+
foreach ($card['detailed']['decoder'] as $decoder):
361393
$totalDecoderFps += floatval($decoder['FrameRate'] ?? 0);
362394
$totalDecoderInFrames += intval($decoder['InFrame'] ?? 0);
363395
$totalDecoderOutFrames += intval($decoder['OutFrame'] ?? 0);
@@ -387,16 +419,12 @@ function formatBitrate($bps) {
387419
</tfoot>
388420
</table>
389421
</div>
390-
</div>
391422
<?php endif; ?>
392423

393-
<!-- Active Encoder Sessions -->
394-
<?php if (!empty($quadraDetailed['encoder'])): ?>
395-
<div class="card mb-3">
396-
<div class="card-header">
397-
<i class="material-icons md-18">output</i> Active Encoder Sessions (<?php echo count($quadraDetailed['encoder']) ?>)
398-
</div>
399-
<div class="card-body table-responsive">
424+
<!-- Active Encoder Sessions -->
425+
<?php if (!empty($card['detailed']['encoder'])): ?>
426+
<h6><i class="material-icons md-18">output</i> Active Encoder Sessions (<?php echo count($card['detailed']['encoder']) ?>)</h6>
427+
<div class="table-responsive mb-3">
400428
<table class="table table-sm table-striped table-hover">
401429
<thead class="thead-highlight text-left">
402430
<tr>
@@ -420,7 +448,7 @@ function formatBitrate($bps) {
420448
$totalEncoderAvgBitrate = 0;
421449
$totalEncoderInFrames = 0;
422450
$totalEncoderOutFrames = 0;
423-
foreach ($quadraDetailed['encoder'] as $encoder):
451+
foreach ($card['detailed']['encoder'] as $encoder):
424452
$totalEncoderFps += floatval($encoder['FrameRate'] ?? 0);
425453
$totalEncoderBitrate += intval($encoder['BR'] ?? 0);
426454
$totalEncoderAvgBitrate += intval($encoder['AvgBR'] ?? 0);
@@ -457,9 +485,12 @@ function formatBitrate($bps) {
457485
</tfoot>
458486
</table>
459487
</div>
460-
</div>
461488
<?php endif; ?>
462489

490+
</div><!-- card-body -->
491+
</div><!-- card -->
492+
<?php endforeach; ?>
493+
463494
<?php endif; ?>
464495
</div><!-- content -->
465496
</div>

0 commit comments

Comments
 (0)