fix(collector): show correct nvme capacity
Some nvme devices do not report their capacity through the usual 'user_capacity' field, instead the total capacity is reported with the 'nvme_total_capacity' field. Fixes: #466
This commit is contained in:
@@ -3,13 +3,14 @@ package detect
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/analogj/scrutiny/collector/pkg/common/shell"
|
"github.com/analogj/scrutiny/collector/pkg/common/shell"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/config"
|
"github.com/analogj/scrutiny/collector/pkg/config"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/models"
|
"github.com/analogj/scrutiny/collector/pkg/models"
|
||||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Detect struct {
|
type Detect struct {
|
||||||
@@ -81,8 +82,9 @@ func (d *Detect) SmartCtlInfo(device *models.Device) error {
|
|||||||
device.SerialNumber = availableDeviceInfo.SerialNumber
|
device.SerialNumber = availableDeviceInfo.SerialNumber
|
||||||
device.Firmware = availableDeviceInfo.FirmwareVersion
|
device.Firmware = availableDeviceInfo.FirmwareVersion
|
||||||
device.RotationSpeed = availableDeviceInfo.RotationRate
|
device.RotationSpeed = availableDeviceInfo.RotationRate
|
||||||
device.Capacity = availableDeviceInfo.UserCapacity.Bytes
|
device.Capacity = availableDeviceInfo.Capacity()
|
||||||
device.FormFactor = availableDeviceInfo.FormFactor.Name
|
device.FormFactor = availableDeviceInfo.FormFactor.Name
|
||||||
|
device.DeviceType = availableDeviceInfo.Device.Type
|
||||||
device.DeviceProtocol = availableDeviceInfo.Device.Protocol
|
device.DeviceProtocol = availableDeviceInfo.Device.Protocol
|
||||||
if len(availableDeviceInfo.Vendor) > 0 {
|
if len(availableDeviceInfo.Vendor) > 0 {
|
||||||
device.Manufacturer = availableDeviceInfo.Vendor
|
device.Manufacturer = availableDeviceInfo.Vendor
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package detect_test
|
package detect_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
mock_shell "github.com/analogj/scrutiny/collector/pkg/common/shell/mock"
|
mock_shell "github.com/analogj/scrutiny/collector/pkg/common/shell/mock"
|
||||||
mock_config "github.com/analogj/scrutiny/collector/pkg/config/mock"
|
mock_config "github.com/analogj/scrutiny/collector/pkg/config/mock"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/detect"
|
"github.com/analogj/scrutiny/collector/pkg/detect"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/models"
|
"github.com/analogj/scrutiny/collector/pkg/models"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDetect_SmartctlScan(t *testing.T) {
|
func TestDetect_SmartctlScan(t *testing.T) {
|
||||||
@@ -23,7 +26,7 @@ func TestDetect_SmartctlScan(t *testing.T) {
|
|||||||
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
||||||
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_simple.json")
|
testScanResults, err := os.ReadFile("testdata/smartctl_scan_simple.json")
|
||||||
fakeShell.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(string(testScanResults), err)
|
fakeShell.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(string(testScanResults), err)
|
||||||
|
|
||||||
d := detect.Detect{
|
d := detect.Detect{
|
||||||
@@ -52,7 +55,7 @@ func TestDetect_SmartctlScan_Megaraid(t *testing.T) {
|
|||||||
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
||||||
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_megaraid.json")
|
testScanResults, err := os.ReadFile("testdata/smartctl_scan_megaraid.json")
|
||||||
fakeShell.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(string(testScanResults), err)
|
fakeShell.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(string(testScanResults), err)
|
||||||
|
|
||||||
d := detect.Detect{
|
d := detect.Detect{
|
||||||
@@ -68,8 +71,8 @@ func TestDetect_SmartctlScan_Megaraid(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(scannedDevices))
|
require.Equal(t, 2, len(scannedDevices))
|
||||||
require.Equal(t, []models.Device{
|
require.Equal(t, []models.Device{
|
||||||
models.Device{DeviceName: "bus/0", DeviceType: "megaraid,0"},
|
{DeviceName: "bus/0", DeviceType: "megaraid,0"},
|
||||||
models.Device{DeviceName: "bus/0", DeviceType: "megaraid,1"},
|
{DeviceName: "bus/0", DeviceType: "megaraid,1"},
|
||||||
}, scannedDevices)
|
}, scannedDevices)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +87,7 @@ func TestDetect_SmartctlScan_Nvme(t *testing.T) {
|
|||||||
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
||||||
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_nvme.json")
|
testScanResults, err := os.ReadFile("testdata/smartctl_scan_nvme.json")
|
||||||
fakeShell.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(string(testScanResults), err)
|
fakeShell.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(string(testScanResults), err)
|
||||||
|
|
||||||
d := detect.Detect{
|
d := detect.Detect{
|
||||||
@@ -100,7 +103,7 @@ func TestDetect_SmartctlScan_Nvme(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(scannedDevices))
|
require.Equal(t, 1, len(scannedDevices))
|
||||||
require.Equal(t, []models.Device{
|
require.Equal(t, []models.Device{
|
||||||
models.Device{DeviceName: "nvme0", DeviceType: "nvme"},
|
{DeviceName: "nvme0", DeviceType: "nvme"},
|
||||||
}, scannedDevices)
|
}, scannedDevices)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +190,8 @@ func TestDetect_TransformDetectedDevices_Raid(t *testing.T) {
|
|||||||
Device: "/dev/twa0",
|
Device: "/dev/twa0",
|
||||||
DeviceType: []string{"3ware,0", "3ware,1", "3ware,2", "3ware,3", "3ware,4", "3ware,5"},
|
DeviceType: []string{"3ware,0", "3ware,1", "3ware,2", "3ware,3", "3ware,4", "3ware,5"},
|
||||||
Ignore: false,
|
Ignore: false,
|
||||||
}})
|
},
|
||||||
|
})
|
||||||
detectedDevices := models.Scan{
|
detectedDevices := models.Scan{
|
||||||
Devices: []models.ScanDevice{
|
Devices: []models.ScanDevice{
|
||||||
{
|
{
|
||||||
@@ -297,3 +301,62 @@ func TestDetect_TransformDetectedDevices_WhenDeviceNotDetected(t *testing.T) {
|
|||||||
require.Equal(t, 1, len(transformedDevices))
|
require.Equal(t, 1, len(transformedDevices))
|
||||||
require.Equal(t, "ata", transformedDevices[0].DeviceType)
|
require.Equal(t, "ata", transformedDevices[0].DeviceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDetect_SmartCtlInfo(t *testing.T) {
|
||||||
|
t.Run("should report nvme info", func(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
const (
|
||||||
|
someArgs = "--info --json"
|
||||||
|
|
||||||
|
// device info
|
||||||
|
someDeviceName = "some-device-name"
|
||||||
|
someModelName = "KCD61LUL3T84"
|
||||||
|
someSerialNumber = "61Q0A05UT7B8"
|
||||||
|
someFirmware = "8002"
|
||||||
|
someDeviceProtocol = "NVMe"
|
||||||
|
someDeviceType = "nvme"
|
||||||
|
someCapacity int64 = 3840755982336
|
||||||
|
)
|
||||||
|
|
||||||
|
fakeConfig := mock_config.NewMockInterface(ctrl)
|
||||||
|
fakeConfig.EXPECT().
|
||||||
|
GetCommandMetricsInfoArgs("/dev/" + someDeviceName).
|
||||||
|
Return(someArgs)
|
||||||
|
fakeConfig.EXPECT().
|
||||||
|
GetString("commands.metrics_smartctl_bin").
|
||||||
|
Return("smartctl")
|
||||||
|
|
||||||
|
someLogger := logrus.WithFields(logrus.Fields{})
|
||||||
|
|
||||||
|
smartctlInfoResults, err := os.ReadFile("testdata/smartctl_info_nvme.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fakeShell := mock_shell.NewMockInterface(ctrl)
|
||||||
|
fakeShell.EXPECT().
|
||||||
|
Command(someLogger, "smartctl", append(strings.Split(someArgs, " "), "/dev/"+someDeviceName), "", gomock.Any()).
|
||||||
|
Return(string(smartctlInfoResults), err)
|
||||||
|
|
||||||
|
d := detect.Detect{
|
||||||
|
Logger: someLogger,
|
||||||
|
Shell: fakeShell,
|
||||||
|
Config: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
someDevice := &models.Device{
|
||||||
|
WWN: "some wwn",
|
||||||
|
DeviceName: someDeviceName,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, d.SmartCtlInfo(someDevice))
|
||||||
|
|
||||||
|
assert.Equal(t, someDeviceName, someDevice.DeviceName)
|
||||||
|
assert.Equal(t, someModelName, someDevice.ModelName)
|
||||||
|
assert.Equal(t, someSerialNumber, someDevice.SerialNumber)
|
||||||
|
assert.Equal(t, someFirmware, someDevice.Firmware)
|
||||||
|
assert.Equal(t, someDeviceProtocol, someDevice.DeviceProtocol)
|
||||||
|
assert.Equal(t, someDeviceType, someDevice.DeviceType)
|
||||||
|
assert.Equal(t, someCapacity, someDevice.Capacity)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"json_format_version": [
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"smartctl": {
|
||||||
|
"version": [
|
||||||
|
7,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"svn_revision": "5155",
|
||||||
|
"platform_info": "x86_64-linux-6.1.69-talos",
|
||||||
|
"build_info": "(local build)",
|
||||||
|
"argv": [
|
||||||
|
"smartctl",
|
||||||
|
"--info",
|
||||||
|
"--json",
|
||||||
|
"/dev/nvme4"
|
||||||
|
],
|
||||||
|
"exit_status": 0
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"name": "/dev/nvme4",
|
||||||
|
"info_name": "/dev/nvme4",
|
||||||
|
"type": "nvme",
|
||||||
|
"protocol": "NVMe"
|
||||||
|
},
|
||||||
|
"model_name": "KCD61LUL3T84",
|
||||||
|
"serial_number": "61Q0A05UT7B8",
|
||||||
|
"firmware_version": "8002",
|
||||||
|
"nvme_pci_vendor": {
|
||||||
|
"id": 7695,
|
||||||
|
"subsystem_id": 7695
|
||||||
|
},
|
||||||
|
"nvme_ieee_oui_identifier": 9233294,
|
||||||
|
"nvme_total_capacity": 3840755982336,
|
||||||
|
"nvme_unallocated_capacity": 0,
|
||||||
|
"nvme_controller_id": 1,
|
||||||
|
"nvme_version": {
|
||||||
|
"string": "1.4",
|
||||||
|
"value": 66560
|
||||||
|
},
|
||||||
|
"nvme_number_of_namespaces": 16,
|
||||||
|
"local_time": {
|
||||||
|
"time_t": 1706045146,
|
||||||
|
"asctime": "Tue Jan 23 21:25:46 2024 UTC"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,10 +28,7 @@ type SmartInfo struct {
|
|||||||
ID uint64 `json:"id"`
|
ID uint64 `json:"id"`
|
||||||
} `json:"wwn"`
|
} `json:"wwn"`
|
||||||
FirmwareVersion string `json:"firmware_version"`
|
FirmwareVersion string `json:"firmware_version"`
|
||||||
UserCapacity struct {
|
UserCapacity UserCapacity `json:"user_capacity"`
|
||||||
Blocks int64 `json:"blocks"`
|
|
||||||
Bytes int64 `json:"bytes"`
|
|
||||||
} `json:"user_capacity"`
|
|
||||||
LogicalBlockSize int `json:"logical_block_size"`
|
LogicalBlockSize int `json:"logical_block_size"`
|
||||||
PhysicalBlockSize int `json:"physical_block_size"`
|
PhysicalBlockSize int `json:"physical_block_size"`
|
||||||
RotationRate int `json:"rotation_rate"`
|
RotationRate int `json:"rotation_rate"`
|
||||||
@@ -211,6 +208,7 @@ type SmartInfo struct {
|
|||||||
SubsystemID int `json:"subsystem_id"`
|
SubsystemID int `json:"subsystem_id"`
|
||||||
} `json:"nvme_pci_vendor"`
|
} `json:"nvme_pci_vendor"`
|
||||||
NvmeIeeeOuiIdentifier int `json:"nvme_ieee_oui_identifier"`
|
NvmeIeeeOuiIdentifier int `json:"nvme_ieee_oui_identifier"`
|
||||||
|
NvmeTotalCapacity int64 `json:"nvme_total_capacity"`
|
||||||
NvmeControllerID int `json:"nvme_controller_id"`
|
NvmeControllerID int `json:"nvme_controller_id"`
|
||||||
NvmeNumberOfNamespaces int `json:"nvme_number_of_namespaces"`
|
NvmeNumberOfNamespaces int `json:"nvme_number_of_namespaces"`
|
||||||
NvmeNamespaces []struct {
|
NvmeNamespaces []struct {
|
||||||
@@ -239,6 +237,22 @@ type SmartInfo struct {
|
|||||||
ScsiErrorCounterLog ScsiErrorCounterLog `json:"scsi_error_counter_log"`
|
ScsiErrorCounterLog ScsiErrorCounterLog `json:"scsi_error_counter_log"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capacity finds the total capacity of the device in bytes, or 0 if unknown.
|
||||||
|
func (s *SmartInfo) Capacity() int64 {
|
||||||
|
switch {
|
||||||
|
case s.NvmeTotalCapacity > 0:
|
||||||
|
return s.NvmeTotalCapacity
|
||||||
|
case s.UserCapacity.Bytes > 0:
|
||||||
|
return s.UserCapacity.Bytes
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserCapacity struct {
|
||||||
|
Blocks int64 `json:"blocks"`
|
||||||
|
Bytes int64 `json:"bytes"`
|
||||||
|
}
|
||||||
|
|
||||||
// Primary Attribute Structs
|
// Primary Attribute Structs
|
||||||
type AtaSmartAttributesTableItem struct {
|
type AtaSmartAttributesTableItem struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSmartInfo_Capacity(t *testing.T) {
|
||||||
|
t.Run("should report nvme capacity", func(t *testing.T) {
|
||||||
|
smartInfo := SmartInfo{
|
||||||
|
UserCapacity: UserCapacity{
|
||||||
|
Bytes: 1234,
|
||||||
|
},
|
||||||
|
NvmeTotalCapacity: 5678,
|
||||||
|
}
|
||||||
|
assert.Equal(t, int64(5678), smartInfo.Capacity())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should report user capacity", func(t *testing.T) {
|
||||||
|
smartInfo := SmartInfo{
|
||||||
|
UserCapacity: UserCapacity{
|
||||||
|
Bytes: 1234,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, int64(1234), smartInfo.Capacity())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should report 0 for unknown capacities", func(t *testing.T) {
|
||||||
|
var smartInfo SmartInfo
|
||||||
|
assert.Zero(t, smartInfo.Capacity())
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user