Stop calling an empty parity slot an emulated disk
mdNumDisabled counts a slot that was never populated, so HOST2 reported "Emulating 1 disk" for a parity2 slot with no disk in it. Emulation is a data-disk state, and an assigned identity is what separates a slot that lost its disk from one that never had one.
This commit is contained in:
@@ -517,6 +517,48 @@ function vv_parity_status(): array {
|
|||||||
$numMissing = (int)($var['mdNumMissing'] ?? 0);
|
$numMissing = (int)($var['mdNumMissing'] ?? 0);
|
||||||
$exitCode = (int)($var['sbSyncExit'] ?? 0);
|
$exitCode = (int)($var['sbSyncExit'] ?? 0);
|
||||||
$errors = (int)($var['sbSyncErrs'] ?? 0);
|
$errors = (int)($var['sbSyncErrs'] ?? 0);
|
||||||
|
|
||||||
|
// How many disks are actually being emulated, which is not mdNumDisabled.
|
||||||
|
//
|
||||||
|
// mdNumDisabled counts every slot the array marks disabled, including a parity slot that was
|
||||||
|
// never populated. HOST2 reports mdNumDisabled=1 with mdNumMissing=0, and the slot is parity2
|
||||||
|
// at DISK_NP_DSBL — an empty second-parity slot. Unraid's own Main page shows nothing for it,
|
||||||
|
// while this card said "Emulating 1 disk" and turned the banner amber.
|
||||||
|
//
|
||||||
|
// Parity is never emulated: reconstructing a parity disk from parity is not a thing. Only data
|
||||||
|
// slots are, so the count is taken from disks.ini and restricted to them. A data disk that is
|
||||||
|
// disabled *is* emulated whether or not it is physically present — a pulled failed drive reads
|
||||||
|
// DISK_NP_DSBL and is genuinely being served from parity — so presence is not the test here,
|
||||||
|
// role is.
|
||||||
|
//
|
||||||
|
// A disabled parity slot is still worth knowing about when a real parity disk fails, so it is
|
||||||
|
// counted separately rather than discarded.
|
||||||
|
//
|
||||||
|
// Both counts require the slot to have an assigned identity. A slot that was never populated
|
||||||
|
// reads id="" device="" size="0" — parity2 on HOST2 is exactly that — while a disk that failed
|
||||||
|
// and was pulled keeps its id, because the array remembers the assignment it is emulating.
|
||||||
|
// Presence is therefore the wrong test and identity is the right one: it separates "no disk was
|
||||||
|
// ever here" from "the disk that belongs here is gone", which look identical in the status
|
||||||
|
// field alone and mean opposite things.
|
||||||
|
$numEmulated = 0;
|
||||||
|
$numParityDisabled = 0;
|
||||||
|
$slot = ''; $slotId = ''; $slotStatus = '';
|
||||||
|
$tally = function () use (&$slot, &$slotId, &$slotStatus, &$numEmulated, &$numParityDisabled) {
|
||||||
|
if ($slot === '' || $slotId === '') return;
|
||||||
|
if (strpos($slotStatus, 'DSBL') === false) return;
|
||||||
|
if (preg_match('/^disk\d+$/', $slot)) $numEmulated++;
|
||||||
|
elseif (preg_match('/^parity\d*$/', $slot)) $numParityDisabled++;
|
||||||
|
};
|
||||||
|
foreach (@file('/var/local/emhttp/disks.ini') ?: [] as $line) {
|
||||||
|
if (preg_match('/^\["([^"]+)"\]/', $line, $m)) {
|
||||||
|
$tally(); // close the previous slot
|
||||||
|
$slot = $m[1]; $slotId = ''; $slotStatus = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('/^id="([^"]*)"/', $line, $m)) $slotId = trim($m[1]);
|
||||||
|
if (preg_match('/^status="([^"]*)"/', $line, $m)) $slotStatus = $m[1];
|
||||||
|
}
|
||||||
|
$tally(); // and the last one, which has no header after it
|
||||||
// Emulated (DISK_DSBL) disks are protected by parity and don't make parity invalid.
|
// Emulated (DISK_DSBL) disks are protected by parity and don't make parity invalid.
|
||||||
// True invalidity: sync errors on the last check, or unprotectable missing slots.
|
// True invalidity: sync errors on the last check, or unprotectable missing slots.
|
||||||
$isValid = $errors === 0 && $numMissing === 0;
|
$isValid = $errors === 0 && $numMissing === 0;
|
||||||
@@ -566,7 +608,12 @@ function vv_parity_status(): array {
|
|||||||
$exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
|
$exitMap = ['0' => 'Completed', '-4' => 'Aborted', '-5' => 'Cancelled'];
|
||||||
return [
|
return [
|
||||||
'valid' => $isValid,
|
'valid' => $isValid,
|
||||||
|
// mdNumDisabled as the array reports it, kept because it is Unraid's own number and
|
||||||
|
// anything comparing against the WebGUI wants it. Not what the card labels, though —
|
||||||
|
// num_emulated is the one that means "data is being served from parity".
|
||||||
'num_disabled' => $numDisabled,
|
'num_disabled' => $numDisabled,
|
||||||
|
'num_emulated' => $numEmulated,
|
||||||
|
'num_parity_disabled' => $numParityDisabled,
|
||||||
'num_missing' => $numMissing,
|
'num_missing' => $numMissing,
|
||||||
'in_progress' => $inProgress,
|
'in_progress' => $inProgress,
|
||||||
'resync_action' => $resyncAction,
|
'resync_action' => $resyncAction,
|
||||||
|
|||||||
@@ -1290,21 +1290,28 @@ function vvPollMonitor(live) {
|
|||||||
return new Date(ts * 1000).toLocaleString([], {weekday:'short',day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
|
return new Date(ts * 1000).toLocaleString([], {weekday:'short',day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
|
||||||
}
|
}
|
||||||
|
|
||||||
const numDisabled = par.num_disabled ?? 0;
|
// num_emulated, not num_disabled: the array counts an empty parity slot as disabled, and
|
||||||
|
// labelling that "Emulating 1 disk" described a failure that was not happening. See
|
||||||
|
// vv_parity_status() for why parity slots are counted separately.
|
||||||
|
const numEmulated = par.num_emulated ?? 0;
|
||||||
|
const numParityDsbl = par.num_parity_disabled ?? 0;
|
||||||
const numMissing = par.num_missing ?? 0;
|
const numMissing = par.num_missing ?? 0;
|
||||||
const degraded = numDisabled > 0 || numMissing > 0;
|
const degraded = numEmulated > 0 || numMissing > 0;
|
||||||
const exitColor = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
|
const exitColor = par.exit_label === 'Completed' ? '#4caf50' : par.exit_label === 'Aborted' ? '#ff9800' : '#f44336';
|
||||||
const errColor = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
|
const errColor = (par.errors ?? 0) > 0 ? '#f44336' : '#444';
|
||||||
const speedStr = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
|
const speedStr = par.last_speed_mb ? ` · ${par.last_speed_mb} MB/s` : '';
|
||||||
const nextDate = vvFmtDate(par.next_ts);
|
const nextDate = vvFmtDate(par.next_ts);
|
||||||
const dueIn = vvDueIn(par.next_ts);
|
const dueIn = vvDueIn(par.next_ts);
|
||||||
const parBannerCls = !valid ? 'vv-banner-err' : degraded || (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';
|
const parBannerCls = !valid ? 'vv-banner-err' : degraded || (par.errors ?? 0) > 0 ? 'vv-banner-warn' : 'vv-banner-ok';
|
||||||
const emulLabel = numDisabled > 0 ? `<span style="font-size:11px;">Emulating ${numDisabled} disk${numDisabled !== 1 ? 's' : ''}</span>` : '';
|
const emulLabel = numEmulated > 0 ? `<span style="font-size:11px;">Emulating ${numEmulated} disk${numEmulated !== 1 ? 's' : ''}</span>` : '';
|
||||||
const missingLabel = numMissing > 0 ? `<span style="font-size:11px;color:#f44336;">${numMissing} slot${numMissing !== 1 ? 's' : ''} missing</span>` : '';
|
const missingLabel = numMissing > 0 ? `<span style="font-size:11px;color:#f44336;">${numMissing} slot${numMissing !== 1 ? 's' : ''} missing</span>` : '';
|
||||||
|
// A failed parity disk is a real fault and still has to surface — it just is not emulation,
|
||||||
|
// and it does not mean data is being reconstructed.
|
||||||
|
const parDsblLabel = numParityDsbl > 0 ? `<span style="font-size:11px;color:#ffb74d;">${numParityDsbl} parity disk${numParityDsbl !== 1 ? 's' : ''} disabled</span>` : '';
|
||||||
|
|
||||||
let html = `<div class="vv-banner ${parBannerCls}">
|
let html = `<div class="vv-banner ${parBannerCls}">
|
||||||
<span>${valid ? '✓ Valid' : '✗ INVALID'}</span>
|
<span>${valid ? '✓ Valid' : '✗ INVALID'}</span>
|
||||||
${emulLabel}${missingLabel}
|
${emulLabel}${missingLabel}${parDsblLabel}
|
||||||
${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}
|
${(par.errors ?? 0) > 0 ? `<span style="font-size:11px;">${par.errors} error${par.errors !== 1 ? 's' : ''}</span>` : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user