diff --git a/agent/smart.go b/agent/smart.go index 108ff260..383c7508 100644 --- a/agent/smart.go +++ b/agent/smart.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os/exec" + "strconv" "strings" "sync" "time" @@ -146,7 +147,7 @@ func (sm *SmartManager) ScanDevices() error { } // CollectSmart collects SMART data for a device -// Collect data using `smartctl --all -j /dev/sdX` or `smartctl --all -j /dev/nvmeX` +// Collect data using `smartctl -d -aj /dev/` when device type is known // Always attempts to parse output even if command fails, as some data may still be available // If collect fails, return error // If collect succeeds, parse the output and update the SmartDataMap @@ -160,7 +161,8 @@ func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error { defer cancel() // Try with -n standby first if we have existing data - cmd := exec.CommandContext(ctx, "smartctl", "-aj", "-n", "standby", deviceInfo.Name) + args := sm.smartctlArgs(deviceInfo, true) + cmd := exec.CommandContext(ctx, "smartctl", args...) output, err := cmd.CombinedOutput() // Check if device is in standby (exit status 2) @@ -174,18 +176,19 @@ func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error { slog.Debug("device in standby but no cached data, collecting initial data", "device", deviceInfo.Name) ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second) defer cancel2() - cmd = exec.CommandContext(ctx2, "smartctl", "-aj", deviceInfo.Name) + args = sm.smartctlArgs(deviceInfo, false) + cmd = exec.CommandContext(ctx2, "smartctl", args...) output, err = cmd.CombinedOutput() } hasValidData := false switch deviceInfo.Type { - case "scsi", "sat", "ata": - // parse SATA/SCSI/ATA devices + case "scsi": + hasValidData, _ = sm.parseSmartForScsi(output) + case "sat", "ata": hasValidData, _ = sm.parseSmartForSata(output) - case "nvme": - // parse nvme devices + case "nvme", "sntasmedia": hasValidData, _ = sm.parseSmartForNvme(output) } @@ -198,6 +201,28 @@ func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error { return nil } +// smartctlArgs returns the arguments for the smartctl command +// based on the device type and whether to include standby mode +func (sm *SmartManager) smartctlArgs(deviceInfo *DeviceInfo, includeStandby bool) []string { + args := make([]string, 0, 7) + + if deviceInfo != nil && deviceInfo.Type != "" { + args = append(args, "-d", deviceInfo.Type) + } + + args = append(args, "-aj") + + if includeStandby { + args = append(args, "-n", "standby") + } + + if deviceInfo != nil { + args = append(args, deviceInfo.Name) + } + + return args +} + // hasDataForDevice checks if we have cached SMART data for a specific device func (sm *SmartManager) hasDataForDevice(deviceName string) bool { sm.Lock() @@ -347,6 +372,87 @@ func getSmartStatus(temperature uint8, passed bool) string { } } +func (sm *SmartManager) parseSmartForScsi(output []byte) (bool, int) { + var data smart.SmartInfoForScsi + + if err := json.Unmarshal(output, &data); err != nil { + return false, 0 + } + + if data.SerialNumber == "" { + slog.Debug("scsi device has no serial number, skipping", "device", data.Device.Name) + return false, data.Smartctl.ExitStatus + } + + sm.Lock() + defer sm.Unlock() + + keyName := data.SerialNumber + if _, ok := sm.SmartDataMap[keyName]; !ok { + sm.SmartDataMap[keyName] = &smart.SmartData{} + } + + smartData := sm.SmartDataMap[keyName] + smartData.ModelName = data.ScsiModelName + smartData.SerialNumber = data.SerialNumber + smartData.FirmwareVersion = data.ScsiRevision + smartData.Capacity = data.UserCapacity.Bytes + smartData.Temperature = data.Temperature.Current + smartData.SmartStatus = getSmartStatus(smartData.Temperature, data.SmartStatus.Passed) + smartData.DiskName = data.Device.Name + smartData.DiskType = data.Device.Type + + attributes := make([]*smart.SmartAttribute, 0, 10) + attributes = append(attributes, &smart.SmartAttribute{Name: "PowerOnHours", RawValue: data.PowerOnTime.Hours}) + attributes = append(attributes, &smart.SmartAttribute{Name: "PowerOnMinutes", RawValue: data.PowerOnTime.Minutes}) + attributes = append(attributes, &smart.SmartAttribute{Name: "GrownDefectList", RawValue: data.ScsiGrownDefectList}) + attributes = append(attributes, &smart.SmartAttribute{Name: "StartStopCycles", RawValue: data.ScsiStartStopCycleCounter.AccumulatedStartStopCycles}) + attributes = append(attributes, &smart.SmartAttribute{Name: "LoadUnloadCycles", RawValue: data.ScsiStartStopCycleCounter.AccumulatedLoadUnloadCycles}) + attributes = append(attributes, &smart.SmartAttribute{Name: "StartStopSpecified", RawValue: data.ScsiStartStopCycleCounter.SpecifiedCycleCountOverDeviceLifetime}) + attributes = append(attributes, &smart.SmartAttribute{Name: "LoadUnloadSpecified", RawValue: data.ScsiStartStopCycleCounter.SpecifiedLoadUnloadCountOverDeviceLifetime}) + + readStats := data.ScsiErrorCounterLog.Read + writeStats := data.ScsiErrorCounterLog.Write + verifyStats := data.ScsiErrorCounterLog.Verify + + attributes = append(attributes, &smart.SmartAttribute{Name: "ReadTotalErrorsCorrected", RawValue: readStats.TotalErrorsCorrected}) + attributes = append(attributes, &smart.SmartAttribute{Name: "ReadTotalUncorrectedErrors", RawValue: readStats.TotalUncorrectedErrors}) + attributes = append(attributes, &smart.SmartAttribute{Name: "ReadCorrectionAlgorithmInvocations", RawValue: readStats.CorrectionAlgorithmInvocations}) + if val := parseScsiGigabytesProcessed(readStats.GigabytesProcessed); val >= 0 { + attributes = append(attributes, &smart.SmartAttribute{Name: "ReadGigabytesProcessed", RawValue: uint64(val)}) + } + attributes = append(attributes, &smart.SmartAttribute{Name: "WriteTotalErrorsCorrected", RawValue: writeStats.TotalErrorsCorrected}) + attributes = append(attributes, &smart.SmartAttribute{Name: "WriteTotalUncorrectedErrors", RawValue: writeStats.TotalUncorrectedErrors}) + attributes = append(attributes, &smart.SmartAttribute{Name: "WriteCorrectionAlgorithmInvocations", RawValue: writeStats.CorrectionAlgorithmInvocations}) + if val := parseScsiGigabytesProcessed(writeStats.GigabytesProcessed); val >= 0 { + attributes = append(attributes, &smart.SmartAttribute{Name: "WriteGigabytesProcessed", RawValue: uint64(val)}) + } + attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyTotalErrorsCorrected", RawValue: verifyStats.TotalErrorsCorrected}) + attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyTotalUncorrectedErrors", RawValue: verifyStats.TotalUncorrectedErrors}) + attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyCorrectionAlgorithmInvocations", RawValue: verifyStats.CorrectionAlgorithmInvocations}) + if val := parseScsiGigabytesProcessed(verifyStats.GigabytesProcessed); val >= 0 { + attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyGigabytesProcessed", RawValue: uint64(val)}) + } + + smartData.Attributes = attributes + sm.SmartDataMap[keyName] = smartData + + return true, data.Smartctl.ExitStatus +} + +func parseScsiGigabytesProcessed(value string) int64 { + if value == "" { + return -1 + } + normalized := strings.ReplaceAll(value, ",", "") + parsed, err := strconv.ParseInt(normalized, 10, 64) + if err != nil { + slog.Debug("failed to parse SCSI gigabytes processed", "value", value, "err", err) + return -1 + } + return parsed +} + // parseSmartForNvme parses the output of smartctl --all -j /dev/nvmeX and updates the SmartDataMap // Returns hasValidData and exitStatus func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) { diff --git a/agent/smart_test.go b/agent/smart_test.go new file mode 100644 index 00000000..70020d65 --- /dev/null +++ b/agent/smart_test.go @@ -0,0 +1,312 @@ +//go:build testing +// +build testing + +package agent + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/henrygd/beszel/internal/entities/smart" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseSmartForScsi(t *testing.T) { + fixturePath := filepath.Join("test-data", "smart", "scsi.json") + data, err := os.ReadFile(fixturePath) + if err != nil { + t.Fatalf("failed reading fixture: %v", err) + } + + sm := &SmartManager{ + SmartDataMap: make(map[string]*smart.SmartData), + } + + hasData, exitStatus := sm.parseSmartForScsi(data) + if !hasData { + t.Fatalf("expected SCSI data to parse successfully") + } + if exitStatus != 0 { + t.Fatalf("expected exit status 0, got %d", exitStatus) + } + + deviceData, ok := sm.SmartDataMap["9YHSDH9B"] + if !ok { + t.Fatalf("expected smart data entry for serial 9YHSDH9B") + } + + if deviceData.ModelName != "YADRO WUH721414AL4204" { + t.Fatalf("unexpected model name: %s", deviceData.ModelName) + } + if deviceData.FirmwareVersion != "C240" { + t.Fatalf("unexpected firmware version: %s", deviceData.FirmwareVersion) + } + if deviceData.DiskName != "/dev/sde" { + t.Fatalf("unexpected disk name: %s", deviceData.DiskName) + } + if deviceData.DiskType != "scsi" { + t.Fatalf("unexpected disk type: %s", deviceData.DiskType) + } + if deviceData.Temperature != 34 { + t.Fatalf("unexpected temperature: %d", deviceData.Temperature) + } + if deviceData.SmartStatus != "PASSED" { + t.Fatalf("unexpected SMART status: %s", deviceData.SmartStatus) + } + if deviceData.Capacity != 14000519643136 { + t.Fatalf("unexpected capacity: %d", deviceData.Capacity) + } + + if len(deviceData.Attributes) == 0 { + t.Fatalf("expected attributes to be populated") + } + + assertAttrValue(t, deviceData.Attributes, "PowerOnHours", 458) + assertAttrValue(t, deviceData.Attributes, "PowerOnMinutes", 25) + assertAttrValue(t, deviceData.Attributes, "GrownDefectList", 0) + assertAttrValue(t, deviceData.Attributes, "StartStopCycles", 2) + assertAttrValue(t, deviceData.Attributes, "LoadUnloadCycles", 418) + assertAttrValue(t, deviceData.Attributes, "ReadGigabytesProcessed", 3641) + assertAttrValue(t, deviceData.Attributes, "WriteGigabytesProcessed", 2124590) + assertAttrValue(t, deviceData.Attributes, "VerifyGigabytesProcessed", 0) +} + +func TestParseSmartForSata(t *testing.T) { + fixturePath := filepath.Join("test-data", "smart", "sda.json") + data, err := os.ReadFile(fixturePath) + require.NoError(t, err) + + sm := &SmartManager{ + SmartDataMap: make(map[string]*smart.SmartData), + } + + hasData, exitStatus := sm.parseSmartForSata(data) + require.True(t, hasData) + assert.Equal(t, 64, exitStatus) + + deviceData, ok := sm.SmartDataMap["9C40918040082"] + require.True(t, ok, "expected smart data entry for serial 9C40918040082") + + assert.Equal(t, "P3-2TB", deviceData.ModelName) + assert.Equal(t, "X0104A0", deviceData.FirmwareVersion) + assert.Equal(t, "/dev/sda", deviceData.DiskName) + assert.Equal(t, "sat", deviceData.DiskType) + assert.Equal(t, uint8(31), deviceData.Temperature) + assert.Equal(t, "PASSED", deviceData.SmartStatus) + assert.Equal(t, uint64(2048408248320), deviceData.Capacity) + if assert.NotEmpty(t, deviceData.Attributes) { + assertAttrValue(t, deviceData.Attributes, "Temperature_Celsius", 31) + } +} + +func TestParseSmartForNvme(t *testing.T) { + fixturePath := filepath.Join("test-data", "smart", "nvme0.json") + data, err := os.ReadFile(fixturePath) + require.NoError(t, err) + + sm := &SmartManager{ + SmartDataMap: make(map[string]*smart.SmartData), + } + + hasData, exitStatus := sm.parseSmartForNvme(data) + require.True(t, hasData) + assert.Equal(t, 0, exitStatus) + + deviceData, ok := sm.SmartDataMap["2024031600129"] + require.True(t, ok, "expected smart data entry for serial 2024031600129") + + assert.Equal(t, "PELADN 512GB", deviceData.ModelName) + assert.Equal(t, "VC2S038E", deviceData.FirmwareVersion) + assert.Equal(t, "/dev/nvme0", deviceData.DiskName) + assert.Equal(t, "nvme", deviceData.DiskType) + assert.Equal(t, uint8(61), deviceData.Temperature) + assert.Equal(t, "PASSED", deviceData.SmartStatus) + assert.Equal(t, uint64(512110190592), deviceData.Capacity) + if assert.NotEmpty(t, deviceData.Attributes) { + assertAttrValue(t, deviceData.Attributes, "PercentageUsed", 0) + assertAttrValue(t, deviceData.Attributes, "DataUnitsWritten", 16040567) + } +} + +func TestHasDataForDevice(t *testing.T) { + sm := &SmartManager{ + SmartDataMap: map[string]*smart.SmartData{ + "serial-1": {DiskName: "/dev/sda"}, + "serial-2": nil, + }, + } + + assert.True(t, sm.hasDataForDevice("/dev/sda")) + assert.False(t, sm.hasDataForDevice("/dev/sdb")) +} + +func TestDevicesSnapshotReturnsCopy(t *testing.T) { + originalDevice := &DeviceInfo{Name: "/dev/sda"} + sm := &SmartManager{ + SmartDevices: []*DeviceInfo{ + originalDevice, + {Name: "/dev/sdb"}, + }, + } + + snapshot := sm.devicesSnapshot() + require.Len(t, snapshot, 2) + + sm.SmartDevices[0] = &DeviceInfo{Name: "/dev/sdz"} + assert.Equal(t, "/dev/sda", snapshot[0].Name) + + snapshot[1] = &DeviceInfo{Name: "/dev/nvme0"} + assert.Equal(t, "/dev/sdb", sm.SmartDevices[1].Name) + + sm.SmartDevices = append(sm.SmartDevices, &DeviceInfo{Name: "/dev/nvme1"}) + assert.Len(t, snapshot, 2) +} + +func TestSmartctlArgs(t *testing.T) { + sm := &SmartManager{} + + sataDevice := &DeviceInfo{Name: "/dev/sda", Type: "sat"} + assert.Equal(t, + []string{"-d", "sat", "-aj", "-n", "standby", "/dev/sda"}, + sm.smartctlArgs(sataDevice, true), + ) + + assert.Equal(t, + []string{"-d", "sat", "-aj", "/dev/sda"}, + sm.smartctlArgs(sataDevice, false), + ) + + assert.Equal(t, + []string{"-aj", "-n", "standby"}, + sm.smartctlArgs(nil, true), + ) +} + +func TestResolveRefreshError(t *testing.T) { + scanErr := errors.New("scan failed") + collectErr := errors.New("collect failed") + + tests := []struct { + name string + devices []*DeviceInfo + data map[string]*smart.SmartData + scanErr error + collectErr error + expectedErr error + expectNoErr bool + }{ + { + name: "no devices returns scan error", + devices: nil, + data: make(map[string]*smart.SmartData), + scanErr: scanErr, + expectedErr: scanErr, + }, + { + name: "has data ignores errors", + devices: []*DeviceInfo{{Name: "/dev/sda"}}, + data: map[string]*smart.SmartData{"serial": {}}, + scanErr: scanErr, + collectErr: collectErr, + expectNoErr: true, + }, + { + name: "collect error preferred", + devices: []*DeviceInfo{{Name: "/dev/sda"}}, + data: make(map[string]*smart.SmartData), + collectErr: collectErr, + expectedErr: collectErr, + }, + { + name: "scan error returned when no data", + devices: []*DeviceInfo{{Name: "/dev/sda"}}, + data: make(map[string]*smart.SmartData), + scanErr: scanErr, + expectedErr: scanErr, + }, + { + name: "no errors returns sentinel", + devices: []*DeviceInfo{{Name: "/dev/sda"}}, + data: make(map[string]*smart.SmartData), + expectedErr: errNoValidSmartData, + }, + { + name: "no devices collect error", + devices: nil, + data: make(map[string]*smart.SmartData), + collectErr: collectErr, + expectedErr: collectErr, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sm := &SmartManager{ + SmartDevices: tt.devices, + SmartDataMap: tt.data, + } + + err := sm.resolveRefreshError(tt.scanErr, tt.collectErr) + if tt.expectNoErr { + assert.NoError(t, err) + return + } + + if tt.expectedErr == nil { + assert.NoError(t, err) + } else { + assert.Equal(t, tt.expectedErr, err) + } + }) + } +} + +func TestParseScan(t *testing.T) { + sm := &SmartManager{ + SmartDataMap: map[string]*smart.SmartData{ + "/dev/sdb": {}, + }, + } + + scanJSON := []byte(`{ + "devices": [ + {"name": "/dev/sda", "type": "sat", "info_name": "/dev/sda [SAT]", "protocol": "ATA"}, + {"name": "/dev/nvme0", "type": "nvme", "info_name": "/dev/nvme0", "protocol": "NVMe"} + ] + }`) + + hasData := sm.parseScan(scanJSON) + assert.True(t, hasData) + + require.Len(t, sm.SmartDevices, 2) + assert.Equal(t, "/dev/sda", sm.SmartDevices[0].Name) + assert.Equal(t, "sat", sm.SmartDevices[0].Type) + assert.Equal(t, "/dev/nvme0", sm.SmartDevices[1].Name) + assert.Equal(t, "nvme", sm.SmartDevices[1].Type) + + _, exists := sm.SmartDataMap["/dev/sdb"] + assert.False(t, exists, "stale smart data entry should be removed") +} + +func assertAttrValue(t *testing.T, attributes []*smart.SmartAttribute, name string, expected uint64) { + t.Helper() + attr := findAttr(attributes, name) + if attr == nil { + t.Fatalf("expected attribute %s to be present", name) + } + if attr.RawValue != expected { + t.Fatalf("unexpected attribute %s value: got %d, want %d", name, attr.RawValue, expected) + } +} + +func findAttr(attributes []*smart.SmartAttribute, name string) *smart.SmartAttribute { + for _, attr := range attributes { + if attr != nil && attr.Name == name { + return attr + } + } + return nil +} diff --git a/agent/test-data/smart/nvme0.json b/agent/test-data/smart/nvme0.json new file mode 100644 index 00000000..6f02d84e --- /dev/null +++ b/agent/test-data/smart/nvme0.json @@ -0,0 +1,272 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 5 + ], + "pre_release": false, + "svn_revision": "5714", + "platform_info": "x86_64-linux-6.17.1-2-cachyos", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-aj", + "/dev/nvme0" + ], + "exit_status": 0 + }, + "local_time": { + "time_t": 1761507494, + "asctime": "Sun Oct 26 15:38:14 2025 EDT" + }, + "device": { + "name": "/dev/nvme0", + "info_name": "/dev/nvme0", + "type": "nvme", + "protocol": "NVMe" + }, + "model_name": "PELADN 512GB", + "serial_number": "2024031600129", + "firmware_version": "VC2S038E", + "nvme_pci_vendor": { + "id": 4332, + "subsystem_id": 4332 + }, + "nvme_ieee_oui_identifier": 57420, + "nvme_controller_id": 1, + "nvme_version": { + "string": "1.4", + "value": 66560 + }, + "nvme_number_of_namespaces": 1, + "nvme_namespaces": [ + { + "id": 1, + "size": { + "blocks": 1000215216, + "bytes": 512110190592 + }, + "capacity": { + "blocks": 1000215216, + "bytes": 512110190592 + }, + "utilization": { + "blocks": 1000215216, + "bytes": 512110190592 + }, + "formatted_lba_size": 512, + "eui64": { + "oui": 57420, + "ext_id": 112094110470 + }, + "features": { + "value": 0, + "thin_provisioning": false, + "na_fields": false, + "dealloc_or_unwritten_block_error": false, + "uid_reuse": false, + "np_fields": false, + "other": 0 + }, + "lba_formats": [ + { + "formatted": true, + "data_bytes": 512, + "metadata_bytes": 0, + "relative_performance": 0 + } + ] + } + ], + "user_capacity": { + "blocks": 1000215216, + "bytes": 512110190592 + }, + "logical_block_size": 512, + "smart_support": { + "available": true, + "enabled": true + }, + "nvme_firmware_update_capabilities": { + "value": 2, + "slots": 1, + "first_slot_is_read_only": false, + "activiation_without_reset": false, + "multiple_update_detection": false, + "other": 0 + }, + "nvme_optional_admin_commands": { + "value": 23, + "security_send_receive": true, + "format_nvm": true, + "firmware_download": true, + "namespace_management": false, + "self_test": true, + "directives": false, + "mi_send_receive": false, + "virtualization_management": false, + "doorbell_buffer_config": false, + "get_lba_status": false, + "command_and_feature_lockdown": false, + "other": 0 + }, + "nvme_optional_nvm_commands": { + "value": 94, + "compare": false, + "write_uncorrectable": true, + "dataset_management": true, + "write_zeroes": true, + "save_select_feature_nonzero": true, + "reservations": false, + "timestamp": true, + "verify": false, + "copy": false, + "other": 0 + }, + "nvme_log_page_attributes": { + "value": 2, + "smart_health_per_namespace": false, + "commands_effects_log": true, + "extended_get_log_page_cmd": false, + "telemetry_log": false, + "persistent_event_log": false, + "supported_log_pages_log": false, + "telemetry_data_area_4": false, + "other": 0 + }, + "nvme_maximum_data_transfer_pages": 32, + "nvme_composite_temperature_threshold": { + "warning": 100, + "critical": 110 + }, + "temperature": { + "op_limit_max": 100, + "critical_limit_max": 110, + "current": 61 + }, + "nvme_power_states": [ + { + "non_operational_state": false, + "relative_read_latency": 0, + "relative_read_throughput": 0, + "relative_write_latency": 0, + "relative_write_throughput": 0, + "entry_latency_us": 230000, + "exit_latency_us": 50000, + "max_power": { + "value": 800, + "scale": 2, + "units_per_watt": 100 + } + }, + { + "non_operational_state": false, + "relative_read_latency": 1, + "relative_read_throughput": 1, + "relative_write_latency": 1, + "relative_write_throughput": 1, + "entry_latency_us": 4000, + "exit_latency_us": 50000, + "max_power": { + "value": 400, + "scale": 2, + "units_per_watt": 100 + } + }, + { + "non_operational_state": false, + "relative_read_latency": 2, + "relative_read_throughput": 2, + "relative_write_latency": 2, + "relative_write_throughput": 2, + "entry_latency_us": 4000, + "exit_latency_us": 250000, + "max_power": { + "value": 300, + "scale": 2, + "units_per_watt": 100 + } + }, + { + "non_operational_state": true, + "relative_read_latency": 3, + "relative_read_throughput": 3, + "relative_write_latency": 3, + "relative_write_throughput": 3, + "entry_latency_us": 5000, + "exit_latency_us": 10000, + "max_power": { + "value": 300, + "scale": 1, + "units_per_watt": 10000 + } + }, + { + "non_operational_state": true, + "relative_read_latency": 4, + "relative_read_throughput": 4, + "relative_write_latency": 4, + "relative_write_throughput": 4, + "entry_latency_us": 54000, + "exit_latency_us": 45000, + "max_power": { + "value": 50, + "scale": 1, + "units_per_watt": 10000 + } + } + ], + "smart_status": { + "passed": true, + "nvme": { + "value": 0 + } + }, + "nvme_smart_health_information_log": { + "nsid": -1, + "critical_warning": 0, + "temperature": 61, + "available_spare": 100, + "available_spare_threshold": 32, + "percentage_used": 0, + "data_units_read": 6573104, + "data_units_written": 16040567, + "host_reads": 63241130, + "host_writes": 253050006, + "controller_busy_time": 0, + "power_cycles": 430, + "power_on_hours": 4399, + "unsafe_shutdowns": 44, + "media_errors": 0, + "num_err_log_entries": 0, + "warning_temp_time": 0, + "critical_comp_time": 0 + }, + "spare_available": { + "current_percent": 100, + "threshold_percent": 32 + }, + "endurance_used": { + "current_percent": 0 + }, + "power_cycle_count": 430, + "power_on_time": { + "hours": 4399 + }, + "nvme_error_information_log": { + "size": 8, + "read": 8, + "unread": 0 + }, + "nvme_self_test_log": { + "nsid": -1, + "current_self_test_operation": { + "value": 0, + "string": "No self-test in progress" + } + } +} diff --git a/agent/test-data/smart/scan.json b/agent/test-data/smart/scan.json new file mode 100644 index 00000000..e8afe96f --- /dev/null +++ b/agent/test-data/smart/scan.json @@ -0,0 +1,36 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 5 + ], + "pre_release": false, + "svn_revision": "5714", + "platform_info": "x86_64-linux-6.17.1-2-cachyos", + "build_info": "(local build)", + "argv": [ + "smartctl", + "--scan", + "-j" + ], + "exit_status": 0 + }, + "devices": [ + { + "name": "/dev/sda", + "info_name": "/dev/sda [SAT]", + "type": "sat", + "protocol": "ATA" + }, + { + "name": "/dev/nvme0", + "info_name": "/dev/nvme0", + "type": "nvme", + "protocol": "NVMe" + } + ] +} diff --git a/agent/test-data/smart/scsi.json b/agent/test-data/smart/scsi.json new file mode 100644 index 00000000..c3db266b --- /dev/null +++ b/agent/test-data/smart/scsi.json @@ -0,0 +1,125 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 3 + ], + "svn_revision": "5338", + "platform_info": "x86_64-linux-6.12.43+deb12-amd64", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-aj", + "/dev/sde" + ], + "exit_status": 0 + }, + "local_time": { + "time_t": 1761502142, + "asctime": "Sun Oct 21 21:09:02 2025 MSK" + }, + "device": { + "name": "/dev/sde", + "info_name": "/dev/sde", + "type": "scsi", + "protocol": "SCSI" + }, + "scsi_vendor": "YADRO", + "scsi_product": "WUH721414AL4204", + "scsi_model_name": "YADRO WUH721414AL4204", + "scsi_revision": "C240", + "scsi_version": "SPC-4", + "user_capacity": { + "blocks": 3418095616, + "bytes": 14000519643136 + }, + "logical_block_size": 4096, + "scsi_lb_provisioning": { + "name": "fully provisioned", + "value": 0, + "management_enabled": { + "name": "LBPME", + "value": 0 + }, + "read_zeros": { + "name": "LBPRZ", + "value": 0 + } + }, + "rotation_rate": 7200, + "form_factor": { + "scsi_value": 2, + "name": "3.5 inches" + }, + "logical_unit_id": "0x5000cca29063dc00", + "serial_number": "9YHSDH9B", + "device_type": { + "scsi_terminology": "Peripheral Device Type [PDT]", + "scsi_value": 0, + "name": "disk" + }, + "scsi_transport_protocol": { + "name": "SAS (SPL-4)", + "value": 6 + }, + "smart_support": { + "available": true, + "enabled": true + }, + "temperature_warning": { + "enabled": true + }, + "smart_status": { + "passed": true + }, + "temperature": { + "current": 34, + "drive_trip": 85 + }, + "power_on_time": { + "hours": 458, + "minutes": 25 + }, + "scsi_start_stop_cycle_counter": { + "year_of_manufacture": "2022", + "week_of_manufacture": "41", + "specified_cycle_count_over_device_lifetime": 50000, + "accumulated_start_stop_cycles": 2, + "specified_load_unload_count_over_device_lifetime": 600000, + "accumulated_load_unload_cycles": 418 + }, + "scsi_grown_defect_list": 0, + "scsi_error_counter_log": { + "read": { + "errors_corrected_by_eccfast": 0, + "errors_corrected_by_eccdelayed": 0, + "errors_corrected_by_rereads_rewrites": 0, + "total_errors_corrected": 0, + "correction_algorithm_invocations": 346, + "gigabytes_processed": "3,641", + "total_uncorrected_errors": 0 + }, + "write": { + "errors_corrected_by_eccfast": 0, + "errors_corrected_by_eccdelayed": 0, + "errors_corrected_by_rereads_rewrites": 0, + "total_errors_corrected": 0, + "correction_algorithm_invocations": 4052, + "gigabytes_processed": "2124,590", + "total_uncorrected_errors": 0 + }, + "verify": { + "errors_corrected_by_eccfast": 0, + "errors_corrected_by_eccdelayed": 0, + "errors_corrected_by_rereads_rewrites": 0, + "total_errors_corrected": 0, + "correction_algorithm_invocations": 223, + "gigabytes_processed": "0,000", + "total_uncorrected_errors": 0 + } + } +} \ No newline at end of file diff --git a/agent/test-data/smart/sda.json b/agent/test-data/smart/sda.json new file mode 100644 index 00000000..22b88866 --- /dev/null +++ b/agent/test-data/smart/sda.json @@ -0,0 +1,1013 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 5 + ], + "pre_release": false, + "svn_revision": "5714", + "platform_info": "x86_64-linux-6.17.1-2-cachyos", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-aj", + "/dev/sda" + ], + "drive_database_version": { + "string": "7.5/5706" + }, + "messages": [ + { + "string": "Warning: This result is based on an Attribute check.", + "severity": "warning" + } + ], + "exit_status": 64 + }, + "local_time": { + "time_t": 1761507466, + "asctime": "Sun Oct 26 15:37:46 2025 EDT" + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda [SAT]", + "type": "sat", + "protocol": "ATA" + }, + "model_name": "P3-2TB", + "serial_number": "9C40918040082", + "firmware_version": "X0104A0", + "user_capacity": { + "blocks": 4000797360, + "bytes": 2048408248320 + }, + "logical_block_size": 512, + "physical_block_size": 512, + "rotation_rate": 0, + "form_factor": { + "ata_value": 3, + "name": "2.5 inches" + }, + "trim": { + "supported": true, + "deterministic": false, + "zeroed": false + }, + "in_smartctl_database": false, + "ata_version": { + "string": "ACS-2 T13/2015-D revision 3", + "major_value": 1008, + "minor_value": 272 + }, + "sata_version": { + "string": "SATA 3.2", + "value": 255 + }, + "interface_speed": { + "max": { + "sata_value": 14, + "string": "6.0 Gb/s", + "units_per_second": 60, + "bits_per_unit": 100000000 + }, + "current": { + "sata_value": 3, + "string": "6.0 Gb/s", + "units_per_second": 60, + "bits_per_unit": 100000000 + } + }, + "smart_support": { + "available": true, + "enabled": true + }, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "offline_data_collection": { + "status": { + "value": 0, + "string": "was never started" + }, + "completion_seconds": 120 + }, + "self_test": { + "status": { + "value": 0, + "string": "completed without error", + "passed": true + }, + "polling_minutes": { + "short": 2, + "extended": 10 + } + }, + "capabilities": { + "values": [ + 17, + 2 + ], + "exec_offline_immediate_supported": true, + "offline_is_aborted_upon_new_cmd": false, + "offline_surface_scan_supported": false, + "self_tests_supported": true, + "conveyance_self_test_supported": false, + "selective_self_test_supported": false, + "attribute_autosave_enabled": false, + "error_logging_supported": true, + "gp_logging_supported": true + } + }, + "ata_sct_capabilities": { + "value": 1, + "error_recovery_control_supported": false, + "feature_control_supported": false, + "data_table_supported": false + }, + "ata_smart_attributes": { + "revision": 1, + "table": [ + { + "id": 1, + "name": "Raw_Read_Error_Rate", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 5, + "name": "Reallocated_Sector_Ct", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 9, + "name": "Power_On_Hours", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 7344, + "string": "7344" + } + }, + { + "id": 12, + "name": "Power_Cycle_Count", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 104, + "string": "104" + } + }, + { + "id": 160, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 161, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 51, + "string": "PO--CK ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 100, + "string": "100" + } + }, + { + "id": 163, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 12, + "string": "12" + } + }, + { + "id": 164, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 4140, + "string": "4140" + } + }, + { + "id": 165, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 3, + "string": "3" + } + }, + { + "id": 166, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 2, + "string": "2" + } + }, + { + "id": 167, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 2, + "string": "2" + } + }, + { + "id": 168, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 5050, + "string": "5050" + } + }, + { + "id": 169, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 100, + "string": "100" + } + }, + { + "id": 175, + "name": "Program_Fail_Count_Chip", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 176, + "name": "Erase_Fail_Count_Chip", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 177, + "name": "Wear_Leveling_Count", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 178, + "name": "Used_Rsvd_Blk_Cnt_Chip", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 181, + "name": "Program_Fail_Cnt_Total", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 182, + "name": "Erase_Fail_Count_Total", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 192, + "name": "Power-Off_Retract_Count", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 98, + "string": "98" + } + }, + { + "id": 194, + "name": "Temperature_Celsius", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 34, + "string": "-O---K ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 31, + "string": "31" + } + }, + { + "id": 195, + "name": "Hardware_ECC_Recovered", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 295843, + "string": "295843" + } + }, + { + "id": 196, + "name": "Reallocated_Event_Count", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 197, + "name": "Current_Pending_Sector", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 198, + "name": "Offline_Uncorrectable", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 199, + "name": "UDMA_CRC_Error_Count", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 131, + "string": "131" + } + }, + { + "id": 232, + "name": "Available_Reservd_Space", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 100, + "string": "100" + } + }, + { + "id": 241, + "name": "Total_LBAs_Written", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 48, + "string": "----CK ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 37763, + "string": "37763" + } + }, + { + "id": 242, + "name": "Total_LBAs_Read", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 48, + "string": "----CK ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 3928, + "string": "3928" + } + }, + { + "id": 245, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 50, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 25604, + "string": "25604" + } + } + ] + }, + "spare_available": { + "current_percent": 100 + }, + "power_on_time": { + "hours": 7344 + }, + "power_cycle_count": 104, + "endurance_used": { + "current_percent": 0 + }, + "temperature": { + "current": 31 + }, + "ata_smart_error_log": { + "summary": { + "revision": 1, + "count": 131, + "logged_count": 5, + "table": [ + { + "error_number": 129, + "lifetime_hours": 0, + "completion_registers": { + "error": 4, + "status": 81, + "count": 0, + "lba": 0, + "device": 64 + }, + "error_description": "Error: ABRT", + "previous_commands": [ + { + "registers": { + "command": 176, + "features": 208, + "count": 1, + "lba": 12734208, + "device": 0, + "device_control": 8 + }, + "powerup_milliseconds": 0, + "command_name": "SMART READ DATA" + }, + { + "registers": { + "command": 176, + "features": 209, + "count": 1, + "lba": 12734209, + "device": 0, + "device_control": 8 + }, + "powerup_milliseconds": 0, + "command_name": "SMART READ ATTRIBUTE THRESHOLDS [OBS-4]" + }, + { + "registers": { + "command": 176, + "features": 218, + "count": 0, + "lba": 12734208, + "device": 0, + "device_control": 8 + }, + "powerup_milliseconds": 0, + "command_name": "SMART RETURN STATUS" + }, + { + "registers": { + "command": 176, + "features": 213, + "count": 1, + "lba": 12734208, + "device": 0, + "device_control": 8 + }, + "powerup_milliseconds": 0, + "command_name": "SMART READ LOG" + }, + { + "registers": { + "command": 236, + "features": 0, + "count": 1, + "lba": 0, + "device": 0, + "device_control": 8 + }, + "powerup_milliseconds": 0, + "command_name": "IDENTIFY DEVICE" + } + ] + }, + { + "error_number": 127, + "lifetime_hours": 0, + "completion_registers": { + "error": 0, + "status": 0, + "count": 0, + "lba": 0, + "device": 0 + }, + "error_description": " at LBA = 0x00000000 = 0", + "previous_commands": [ + { + "registers": { + "command": 97, + "features": 8, + "count": 0, + "lba": 919080, + "device": 0, + "device_control": 0 + }, + "powerup_milliseconds": 0, + "command_name": "WRITE FPDMA QUEUED" + }, + { + "registers": { + "command": 97, + "features": 8, + "count": 0, + "lba": 919080, + "device": 0, + "device_control": 0 + }, + "powerup_milliseconds": 0, + "command_name": "WRITE FPDMA QUEUED" + }, + { + "registers": { + "command": 97, + "features": 8, + "count": 0, + "lba": 919080, + "device": 0, + "device_control": 0 + }, + "powerup_milliseconds": 0, + "command_name": "WRITE FPDMA QUEUED" + }, + { + "registers": { + "command": 97, + "features": 8, + "count": 0, + "lba": 919080, + "device": 0, + "device_control": 0 + }, + "powerup_milliseconds": 0, + "command_name": "WRITE FPDMA QUEUED" + }, + { + "registers": { + "command": 97, + "features": 8, + "count": 0, + "lba": 919080, + "device": 0, + "device_control": 0 + }, + "powerup_milliseconds": 0, + "command_name": "WRITE FPDMA QUEUED" + } + ] + } + ] + } + }, + "ata_smart_self_test_log": { + "standard": { + "revision": 1, + "table": [ + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 23, + "string": "Aborted by host", + "remaining_percent": 70 + }, + "lifetime_hours": 0 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 23, + "string": "Aborted by host", + "remaining_percent": 70 + }, + "lifetime_hours": 0 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 23, + "string": "Aborted by host", + "remaining_percent": 70 + }, + "lifetime_hours": 0 + } + ], + "count": 3, + "error_count_total": 0, + "error_count_outdated": 0 + } + } +} diff --git a/internal/entities/smart/smart.go b/internal/entities/smart/smart.go index e25c8291..b141ef2f 100644 --- a/internal/entities/smart/smart.go +++ b/internal/entities/smart/smart.go @@ -163,6 +163,11 @@ type TemperatureInfo struct { Current uint8 `json:"current"` } +type TemperatureInfoScsi struct { + Current uint8 `json:"current"` + DriveTrip uint8 `json:"drive_trip"` +} + // type SelectiveSelfTestTable struct { // LbaMin int `json:"lba_min"` // LbaMax int `json:"lba_max"` @@ -235,6 +240,54 @@ type SmartInfoForSata struct { // AtaSmartSelectiveSelfTestLog AtaSmartSelectiveSelfTestLog `json:"ata_smart_selective_self_test_log"` } +type ScsiErrorCounter struct { + ErrorsCorrectedByECCFast uint64 `json:"errors_corrected_by_eccfast"` + ErrorsCorrectedByECCDelayed uint64 `json:"errors_corrected_by_eccdelayed"` + ErrorsCorrectedByRereadsRewrites uint64 `json:"errors_corrected_by_rereads_rewrites"` + TotalErrorsCorrected uint64 `json:"total_errors_corrected"` + CorrectionAlgorithmInvocations uint64 `json:"correction_algorithm_invocations"` + GigabytesProcessed string `json:"gigabytes_processed"` + TotalUncorrectedErrors uint64 `json:"total_uncorrected_errors"` +} + +type ScsiErrorCounterLog struct { + Read ScsiErrorCounter `json:"read"` + Write ScsiErrorCounter `json:"write"` + Verify ScsiErrorCounter `json:"verify"` +} + +type ScsiStartStopCycleCounter struct { + YearOfManufacture string `json:"year_of_manufacture"` + WeekOfManufacture string `json:"week_of_manufacture"` + SpecifiedCycleCountOverDeviceLifetime uint64 `json:"specified_cycle_count_over_device_lifetime"` + AccumulatedStartStopCycles uint64 `json:"accumulated_start_stop_cycles"` + SpecifiedLoadUnloadCountOverDeviceLifetime uint64 `json:"specified_load_unload_count_over_device_lifetime"` + AccumulatedLoadUnloadCycles uint64 `json:"accumulated_load_unload_cycles"` +} + +type PowerOnTimeScsi struct { + Hours uint64 `json:"hours"` + Minutes uint64 `json:"minutes"` +} + +type SmartInfoForScsi struct { + Smartctl SmartctlInfoLegacy `json:"smartctl"` + Device DeviceInfo `json:"device"` + ScsiVendor string `json:"scsi_vendor"` + ScsiProduct string `json:"scsi_product"` + ScsiModelName string `json:"scsi_model_name"` + ScsiRevision string `json:"scsi_revision"` + ScsiVersion string `json:"scsi_version"` + SerialNumber string `json:"serial_number"` + UserCapacity UserCapacity `json:"user_capacity"` + Temperature TemperatureInfoScsi `json:"temperature"` + SmartStatus SmartStatusInfo `json:"smart_status"` + PowerOnTime PowerOnTimeScsi `json:"power_on_time"` + ScsiStartStopCycleCounter ScsiStartStopCycleCounter `json:"scsi_start_stop_cycle_counter"` + ScsiGrownDefectList uint64 `json:"scsi_grown_defect_list"` + ScsiErrorCounterLog ScsiErrorCounterLog `json:"scsi_error_counter_log"` +} + // type AtaSmartErrorLog struct { // Summary SummaryInfo `json:"summary"` // }