diff --git a/modules/sensors/README.md b/modules/sensors/README.md index 741e8cfe..5855f10e 100644 --- a/modules/sensors/README.md +++ b/modules/sensors/README.md @@ -49,13 +49,16 @@ another process, and go on whatever the drives do. - `-n standby,2,3` makes smartctl stop with status 2 when the drive is in standby or asleep. The drive reader then keeps the reading before, and since when the drive has been asleep. A drive counts as asleep on status 2 with either an ATA power mode of STANDBY or SLEEP, or smartctl's own words "Device is in - mode, exit(2)" (a SAS drive, or one the operating system powered down). An open failure is - neither, whatever its words. + mode, exit(2)" (a SAS drive, whose modes hold spaces such as STANDBY BY TIMER or LOW POWER, or + one the operating system powered down). An open failure is neither, whatever its words. - It stops with status 3 when the drive cannot say (a USB bridge that does not pass CHECK POWER MODE on). Such a drive is not read, and is said as not read. -- smartctl is told the drive's kind (`-d nvme`, `-d sat` for an ATA drive or one behind USB, `-d scsi`) - so it does not probe. A drive of a kind the module cannot tell (virtio, MMC) is read with smartctl's own - detection. +- smartctl is told the drive's kind (`-d nvme`, `-d sat` for an ATA drive or any drive behind USB, + `-d scsi`) so it does not probe. A drive behind a USB-to-NVMe enclosure or a vendor's own bridge does not + answer to `sat`, and is said as not read. A drive of a kind the module cannot tell (virtio, MMC) is read + with smartctl's own detection. +- Known limits: a smartctl stuck in the kernel outlives the drive reader, and a restarted reader starts a + new read beside it; all drives are read at once, with no cap per controller or bridge. - Reading SMART from an awake drive can restart its standby timer, so a drive read more often than its timer is set for never spins down. No mesh tool reads a drive's standby timer, so whether the servers' drives spin down is not known. A spinning drive is therefore read once an hour, timed from its last diff --git a/modules/sensors/cmd/sensors/drives.go b/modules/sensors/cmd/sensors/drives.go index 6657dc70..42403b06 100644 --- a/modules/sensors/cmd/sensors/drives.go +++ b/modules/sensors/cmd/sensors/drives.go @@ -540,7 +540,10 @@ func asleep(s smartJSON) bool { return false } -var sleepWords = regexp.MustCompile(`^Device is in [A-Z_]+( \(OS\))? mode, exit\(2\)$`) +// sleepWords are smartctl's words when -n standby stopped it: an ATA mode (STANDBY, SLEEP), a SAS mode, +// which holds spaces (STANDBY BY TIMER, STANDBY_Y BY COMMAND, LOW POWER), or STANDBY (OS). An idle mode +// never stops smartctl under -n standby, so it never appears here. +var sleepWords = regexp.MustCompile(`^Device is in [A-Z_]+( [A-Z_]+)*( \(OS\))? mode, exit\(2\)$`) func saysNoPowerMode(s smartJSON) bool { for _, msg := range s.Smartctl.Messages { diff --git a/modules/sensors/cmd/sensors/second_review_test.go b/modules/sensors/cmd/sensors/second_review_test.go index 36f6e30a..21ff3386 100644 --- a/modules/sensors/cmd/sensors/second_review_test.go +++ b/modules/sensors/cmd/sensors/second_review_test.go @@ -103,18 +103,22 @@ func TestABoardChipsOwnSensorDoesNotStopItsFanAndACPUSensorDoes(t *testing.T) { } } -// D: a SAS drive in standby says so in words and no ATA power mode; status 2 with those words is asleep. +// D: a SAS drive in standby says so in words and no ATA power mode, and its modes hold spaces +// (scsiprint.cpp of smartmontools 7.5); status 2 with those words is asleep. An idle mode never stops +// smartctl under -n standby, so it never says so. func TestASASDriveInStandbyIsAsleep(t *testing.T) { - f := newFake(t) - f.drive("sdm", "ST4000NM0023", "SEAGATE ", 100, true) - d := f.machine().BlockDrives()[0] - sas := `{"smartctl":{"messages":[{"string":"Device is in STANDBY mode, exit(2)","severity":"information"}],"exit_status":2},` + - `"device":{"name":"/dev/sdm","type":"scsi","protocol":"SCSI"}}` - f.answers[smartctlLine("sdm", "scsi")] = Ran{Stdout: sas, Status: 2} - before := DriveReading{ReadAt: f.now.Add(-time.Hour), Smartctl: []byte(ataPassed)} - got := f.machine().ReadDrive(d, &before) - if !got.Asleep || string(got.Smartctl) != ataPassed || got.AsleepSince == nil { - t.Errorf("a SAS drive in standby keeps its reading: %+v", got) + for _, mode := range []string{"STANDBY BY TIMER", "STANDBY BY COMMAND", "STANDBY_Y BY TIMER", "STANDBY_Y BY COMMAND", "LOW POWER", "SLEEP"} { + f := newFake(t) + f.drive("sdm", "ST4000NM0023", "SEAGATE ", 100, true) + d := f.machine().BlockDrives()[0] + sas := `{"smartctl":{"messages":[{"string":"Device is in ` + mode + ` mode, exit(2)","severity":"information"}],"exit_status":2},` + + `"device":{"name":"/dev/sdm","type":"scsi","protocol":"SCSI"}}` + f.answers[smartctlLine("sdm", "scsi")] = Ran{Stdout: sas, Status: 2} + before := DriveReading{ReadAt: f.now.Add(-time.Hour), Smartctl: []byte(ataPassed)} + got := f.machine().ReadDrive(d, &before) + if !got.Asleep || string(got.Smartctl) != ataPassed || got.AsleepSince == nil { + t.Errorf("a SAS drive in %s keeps its reading: %+v", mode, got) + } } }