add NVMe drive info [model num, serial num, drive temp. etc.] (#10613)
* add NVMe drive info [model num, serial num, drive temp. etc.] * Ignore fuse partitions * Add the nvme logic only for linux * Move smart/nvme structs to a separate file Co-authored-by: wlan0 <sidharthamn@gmail.com>master
parent
017954e7ea
commit
f1418a50f0
@ -1,4 +1,4 @@ |
||||
// +build freebsd netbsd openbsd solaris
|
||||
// +build !linux
|
||||
|
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
@ -1,3 +1,5 @@ |
||||
// +build !linux
|
||||
|
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
File diff suppressed because one or more lines are too long
@ -0,0 +1,89 @@ |
||||
// +build linux
|
||||
|
||||
/* |
||||
* MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
/* |
||||
* This file has been adopted and then modified from Daniel Swarbrick's smart |
||||
* project residing at https://github.com/dswarbrick/smart
|
||||
* |
||||
*/ |
||||
|
||||
package smart |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"unsafe" |
||||
|
||||
"github.com/dswarbrick/smart/ioctl" |
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
// NVMe admin disk query constants
|
||||
const ( |
||||
NvmeAdminGetLogPage = 0x02 |
||||
NvmeAdminIdentify = 0x06 |
||||
) |
||||
|
||||
var ( |
||||
nvmeIoctlAdminCmd = ioctl.Iowr('N', 0x41, unsafe.Sizeof(nvmePassthruCommand{})) |
||||
) |
||||
|
||||
// NewNVMeDevice creates a new NVMeDevice struct with name
|
||||
func NewNVMeDevice(name string) *NVMeDevice { |
||||
return &NVMeDevice{name, -1} |
||||
} |
||||
|
||||
// Open - open device file to find kernel info
|
||||
func (d *NVMeDevice) Open() (err error) { |
||||
d.fd, err = unix.Open(d.Name, unix.O_RDWR, 0600) |
||||
return err |
||||
} |
||||
|
||||
// Close - closes device file
|
||||
func (d *NVMeDevice) Close() error { |
||||
return unix.Close(d.fd) |
||||
} |
||||
|
||||
func (d *NVMeDevice) readLogPage(logID uint8, buf *[]byte) error { |
||||
bufLen := len(*buf) |
||||
|
||||
if (bufLen < 4) || (bufLen > 0x4000) || (bufLen%4 != 0) { |
||||
return fmt.Errorf("Invalid buffer size") |
||||
} |
||||
|
||||
cmd := nvmePassthruCommand{ |
||||
opcode: NvmeAdminGetLogPage, |
||||
nsid: 0xffffffff, // FIXME
|
||||
addr: uint64(uintptr(unsafe.Pointer(&(*buf)[0]))), |
||||
dataLen: uint32(bufLen), |
||||
cdw10: uint32(logID) | (((uint32(bufLen) / 4) - 1) << 16), |
||||
} |
||||
|
||||
return ioctl.Ioctl(uintptr(d.fd), nvmeIoctlAdminCmd, uintptr(unsafe.Pointer(&cmd))) |
||||
} |
||||
|
||||
// le128ToBigInt takes a little-endian 16-byte slice and returns a *big.Int representing it.
|
||||
func le128ToBigInt(buf [16]byte) *big.Int { |
||||
// Int.SetBytes() expects big-endian input, so reverse the bytes locally first
|
||||
rev := make([]byte, 16) |
||||
for x := 0; x < 16; x++ { |
||||
rev[x] = buf[16-x-1] |
||||
} |
||||
|
||||
return new(big.Int).SetBytes(rev) |
||||
} |
@ -0,0 +1,153 @@ |
||||
// +build linux
|
||||
|
||||
/* |
||||
* MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package smart |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"regexp" |
||||
"strings" |
||||
"unsafe" |
||||
|
||||
"github.com/dswarbrick/smart/drivedb" |
||||
"github.com/dswarbrick/smart/ioctl" |
||||
"github.com/dswarbrick/smart/scsi" |
||||
"github.com/dswarbrick/smart/utils" |
||||
|
||||
"gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
// GetInfo - gets info about device
|
||||
func GetInfo(device string) (Info, error) { |
||||
info := Info{ |
||||
Device: device, |
||||
} |
||||
|
||||
var db drivedb.DriveDb |
||||
dec := yaml.NewDecoder(bytes.NewBuffer(MustAsset("drivedb.yaml"))) |
||||
|
||||
err := dec.Decode(&db) |
||||
if err != nil { |
||||
return info, err |
||||
} |
||||
|
||||
for i, d := range db.Drives { |
||||
db.Drives[i].CompiledRegexp, _ = regexp.Compile(d.ModelRegex) |
||||
} |
||||
|
||||
if strings.HasPrefix(device, "/dev/nvme") { |
||||
d := NewNVMeDevice(device) |
||||
if err := d.Open(); err != nil { |
||||
return info, err |
||||
} |
||||
nvmeInfo, err := getNvmeInfo(d) |
||||
if err != nil { |
||||
return info, err |
||||
} |
||||
info.Nvme = nvmeInfo |
||||
return info, nil |
||||
} |
||||
|
||||
d, err := scsi.OpenSCSIAutodetect(device) |
||||
if err != nil { |
||||
return info, err |
||||
} |
||||
|
||||
switch dev := d.(type) { |
||||
case *scsi.SCSIDevice: |
||||
scsiInfo, err := getScsiInfo(dev) |
||||
if err != nil { |
||||
return info, err |
||||
} |
||||
info.Scsi = scsiInfo |
||||
case *scsi.SATDevice: |
||||
ataInfo, err := getAtaInfo(dev) |
||||
if err != nil { |
||||
return info, err |
||||
} |
||||
info.Ata = ataInfo |
||||
} |
||||
|
||||
return info, nil |
||||
} |
||||
|
||||
func getNvmeInfo(d *NVMeDevice) (*NvmeInfo, error) { |
||||
buf := make([]byte, 4096) |
||||
nvmeInfo := &NvmeInfo{} |
||||
|
||||
cmd := nvmePassthruCommand{ |
||||
opcode: NvmeAdminIdentify, |
||||
nsid: 0, // Namespace 0, since we are identifying the controller
|
||||
addr: uint64(uintptr(unsafe.Pointer(&buf[0]))), |
||||
dataLen: uint32(len(buf)), |
||||
cdw10: 1, // Identify controller
|
||||
} |
||||
|
||||
if err := ioctl.Ioctl(uintptr(d.fd), nvmeIoctlAdminCmd, uintptr(unsafe.Pointer(&cmd))); err != nil { |
||||
return nvmeInfo, err |
||||
} |
||||
|
||||
var controller nvmeIdentController |
||||
binary.Read(bytes.NewBuffer(buf[:]), utils.NativeEndian, &controller) |
||||
|
||||
nvmeInfo.VendorID = strings.TrimSpace(fmt.Sprintf("%#04x", controller.VendorID)) |
||||
nvmeInfo.ModelNum = strings.TrimSpace(fmt.Sprintf("%s", controller.ModelNumber)) |
||||
nvmeInfo.SerialNum = strings.TrimSpace(fmt.Sprintf("%s", controller.SerialNumber)) |
||||
nvmeInfo.FirmwareVersion = strings.TrimSpace(fmt.Sprintf("%s", controller.Firmware)) |
||||
nvmeInfo.MaxDataTransferPages = 1 << controller.Mdts |
||||
|
||||
buf2 := make([]byte, 512) |
||||
|
||||
// Read SMART log
|
||||
if err := d.readLogPage(0x02, &buf2); err != nil { |
||||
return nvmeInfo, err |
||||
} |
||||
|
||||
var sl nvmeSMARTLog |
||||
binary.Read(bytes.NewBuffer(buf2[:]), utils.NativeEndian, &sl) |
||||
|
||||
unitsRead := le128ToBigInt(sl.DataUnitsRead) |
||||
unitsWritten := le128ToBigInt(sl.DataUnitsWritten) |
||||
|
||||
nvmeInfo.CriticalWarning = fmt.Sprintf("%x", sl.CritWarning) |
||||
nvmeInfo.Temperature = fmt.Sprintf("%d Celsius", |
||||
((uint16(sl.Temperature[1])<<8)|uint16(sl.Temperature[0]))-273) // Kelvin to degrees Celsius
|
||||
nvmeInfo.SpareAvailable = fmt.Sprintf("%d%%", sl.AvailSpare) |
||||
nvmeInfo.SpareThreshold = fmt.Sprintf("%d%%", sl.SpareThresh) |
||||
nvmeInfo.DataUnitsReadBytes = unitsRead |
||||
nvmeInfo.DataUnitsWrittenBytes = unitsWritten |
||||
nvmeInfo.HostReadCommands = le128ToBigInt(sl.HostReads) |
||||
nvmeInfo.HostWriteCommands = le128ToBigInt(sl.HostWrites) |
||||
nvmeInfo.ControllerBusyTime = le128ToBigInt(sl.CtrlBusyTime) |
||||
nvmeInfo.PowerCycles = le128ToBigInt(sl.PowerCycles) |
||||
nvmeInfo.PowerOnHours = le128ToBigInt(sl.PowerOnHours) |
||||
nvmeInfo.UnsafeShutdowns = le128ToBigInt(sl.UnsafeShutdowns) |
||||
nvmeInfo.MediaAndDataIntegrityErrors = le128ToBigInt(sl.MediaErrors) |
||||
|
||||
return nvmeInfo, nil |
||||
} |
||||
|
||||
func getScsiInfo(d *scsi.SCSIDevice) (*ScsiInfo, error) { |
||||
return &ScsiInfo{}, nil |
||||
} |
||||
|
||||
func getAtaInfo(d *scsi.SATDevice) (*AtaInfo, error) { |
||||
return &AtaInfo{}, nil |
||||
} |
@ -0,0 +1,239 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package smart |
||||
|
||||
import "math/big" |
||||
|
||||
// Defined in <linux/nvme_ioctl.h>
|
||||
//nolint:structcheck
|
||||
type nvmePassthruCommand struct { |
||||
opcode uint8 |
||||
flags uint8 |
||||
rsvd1 uint16 |
||||
nsid uint32 |
||||
cdw2 uint32 |
||||
cdw3 uint32 |
||||
metadata uint64 |
||||
addr uint64 |
||||
metadataLen uint32 |
||||
dataLen uint32 |
||||
cdw10 uint32 |
||||
cdw11 uint32 |
||||
cdw12 uint32 |
||||
cdw13 uint32 |
||||
cdw14 uint32 |
||||
cdw15 uint32 |
||||
timeoutMS uint32 |
||||
result uint32 |
||||
} // 72 bytes
|
||||
|
||||
type nvmeIdentPowerState struct { |
||||
MaxPower uint16 // Centiwatts
|
||||
Rsvd2 uint8 |
||||
Flags uint8 |
||||
EntryLat uint32 // Microseconds
|
||||
ExitLat uint32 // Microseconds
|
||||
ReadTput uint8 |
||||
ReadLat uint8 |
||||
WriteTput uint8 |
||||
WriteLat uint8 |
||||
IdlePower uint16 |
||||
IdleScale uint8 |
||||
Rsvd19 uint8 |
||||
ActivePower uint16 |
||||
ActiveWorkScale uint8 |
||||
Rsvd23 [9]byte |
||||
} |
||||
|
||||
type nvmeIdentController struct { |
||||
VendorID uint16 // PCI Vendor ID
|
||||
Ssvid uint16 // PCI Subsystem Vendor ID
|
||||
SerialNumber [20]byte // Serial Number
|
||||
ModelNumber [40]byte // Model Number
|
||||
Firmware [8]byte // Firmware Revision
|
||||
Rab uint8 // Recommended Arbitration Burst
|
||||
IEEE [3]byte // IEEE OUI Identifier
|
||||
Cmic uint8 // Controller Multi-Path I/O and Namespace Sharing Capabilities
|
||||
Mdts uint8 // Maximum Data Transfer Size
|
||||
Cntlid uint16 // Controller ID
|
||||
Ver uint32 // Version
|
||||
Rtd3r uint32 // RTD3 Resume Latency
|
||||
Rtd3e uint32 // RTD3 Entry Latency
|
||||
Oaes uint32 // Optional Asynchronous Events Supported
|
||||
Rsvd96 [160]byte // ...
|
||||
Oacs uint16 // Optional Admin Command Support
|
||||
ACL uint8 // Abort Command Limit
|
||||
Aerl uint8 // Asynchronous Event Request Limit
|
||||
Frmw uint8 // Firmware Updates
|
||||
Lpa uint8 // Log Page Attributes
|
||||
Elpe uint8 // Error Log Page Entries
|
||||
Npss uint8 // Number of Power States Support
|
||||
Avscc uint8 // Admin Vendor Specific Command Configuration
|
||||
Apsta uint8 // Autonomous Power State Transition Attributes
|
||||
Wctemp uint16 // Warning Composite Temperature Threshold
|
||||
Cctemp uint16 // Critical Composite Temperature Threshold
|
||||
Mtfa uint16 // Maximum Time for Firmware Activation
|
||||
Hmpre uint32 // Host Memory Buffer Preferred Size
|
||||
Hmmin uint32 // Host Memory Buffer Minimum Size
|
||||
Tnvmcap [16]byte // Total NVM Capacity
|
||||
Unvmcap [16]byte // Unallocated NVM Capacity
|
||||
Rpmbs uint32 // Replay Protected Memory Block Support
|
||||
Rsvd316 [196]byte // ...
|
||||
Sqes uint8 // Submission Queue Entry Size
|
||||
Cqes uint8 // Completion Queue Entry Size
|
||||
Rsvd514 [2]byte // (defined in NVMe 1.3 spec)
|
||||
Nn uint32 // Number of Namespaces
|
||||
Oncs uint16 // Optional NVM Command Support
|
||||
Fuses uint16 // Fused Operation Support
|
||||
Fna uint8 // Format NVM Attributes
|
||||
Vwc uint8 // Volatile Write Cache
|
||||
Awun uint16 // Atomic Write Unit Normal
|
||||
Awupf uint16 // Atomic Write Unit Power Fail
|
||||
Nvscc uint8 // NVM Vendor Specific Command Configuration
|
||||
Rsvd531 uint8 // ...
|
||||
Acwu uint16 // Atomic Compare & Write Unit
|
||||
Rsvd534 [2]byte // ...
|
||||
Sgls uint32 // SGL Support
|
||||
Rsvd540 [1508]byte // ...
|
||||
Psd [32]nvmeIdentPowerState // Power State Descriptors
|
||||
Vs [1024]byte // Vendor Specific
|
||||
} // 4096 bytes
|
||||
|
||||
type nvmeLBAF struct { |
||||
Ms uint16 |
||||
Ds uint8 |
||||
Rp uint8 |
||||
} |
||||
|
||||
//nolint:deadcode
|
||||
type nvmeIdentNamespace struct { |
||||
Nsze uint64 |
||||
Ncap uint64 |
||||
Nuse uint64 |
||||
Nsfeat uint8 |
||||
Nlbaf uint8 |
||||
Flbas uint8 |
||||
Mc uint8 |
||||
Dpc uint8 |
||||
Dps uint8 |
||||
Nmic uint8 |
||||
Rescap uint8 |
||||
Fpi uint8 |
||||
Rsvd33 uint8 |
||||
Nawun uint16 |
||||
Nawupf uint16 |
||||
Nacwu uint16 |
||||
Nabsn uint16 |
||||
Nabo uint16 |
||||
Nabspf uint16 |
||||
Rsvd46 [2]byte |
||||
Nvmcap [16]byte |
||||
Rsvd64 [40]byte |
||||
Nguid [16]byte |
||||
EUI64 [8]byte |
||||
Lbaf [16]nvmeLBAF |
||||
Rsvd192 [192]byte |
||||
Vs [3712]byte |
||||
} // 4096 bytes
|
||||
|
||||
type nvmeSMARTLog struct { |
||||
CritWarning uint8 |
||||
Temperature [2]uint8 |
||||
AvailSpare uint8 |
||||
SpareThresh uint8 |
||||
PercentUsed uint8 |
||||
Rsvd6 [26]byte |
||||
DataUnitsRead [16]byte |
||||
DataUnitsWritten [16]byte |
||||
HostReads [16]byte |
||||
HostWrites [16]byte |
||||
CtrlBusyTime [16]byte |
||||
PowerCycles [16]byte |
||||
PowerOnHours [16]byte |
||||
UnsafeShutdowns [16]byte |
||||
MediaErrors [16]byte |
||||
NumErrLogEntries [16]byte |
||||
WarningTempTime uint32 |
||||
CritCompTime uint32 |
||||
TempSensor [8]uint16 |
||||
Rsvd216 [296]byte |
||||
} // 512 bytes
|
||||
|
||||
// NVMeDevice represents drive data about NVMe drives
|
||||
type NVMeDevice struct { |
||||
Name string |
||||
fd int |
||||
} |
||||
|
||||
// Info contains S.M.A.R.T data about the drive
|
||||
type Info struct { |
||||
Device string `json:"device"` |
||||
|
||||
Scsi *ScsiInfo `json:"scsi,omitempty"` |
||||
Nvme *NvmeInfo `json:"nvme,omitempty"` |
||||
Ata *AtaInfo `json:"ata,omitempty"` |
||||
|
||||
Error string `json:"error,omitempty"` |
||||
} |
||||
|
||||
// AtaInfo contains ATA drive info
|
||||
type AtaInfo struct { |
||||
LUWWNDeviceID string `json:"scsiLuWWNDeviceID,omitempty"` |
||||
SerialNum string `json:"serialNum,omitempty"` |
||||
ModelNum string `json:"modelNum,omitempty"` |
||||
FirmwareRevision string `json:"firmwareRevision,omitempty"` |
||||
RotationRate string `json:"RotationRate,omitempty"` |
||||
ATAMajorVersion string `json:"MajorVersion,omitempty"` |
||||
ATAMinorVersion string `json:"MinorVersion,omitempty"` |
||||
SmartSupportAvailable bool `json:"smartSupportAvailable,omitempty"` |
||||
SmartSupportEnabled bool `json:"smartSupportEnabled,omitempty"` |
||||
ErrorLog string `json:"smartErrorLog,omitempty"` |
||||
Transport string `json:"transport,omitempty"` |
||||
} |
||||
|
||||
// ScsiInfo contains SCSI drive Info
|
||||
type ScsiInfo struct { |
||||
CapacityBytes int64 `json:"scsiCapacityBytes,omitempty"` |
||||
ModeSenseBuf string `json:"scsiModeSenseBuf,omitempty"` |
||||
RespLen int64 `json:"scsirespLen,omitempty"` |
||||
BdLen int64 `json:"scsiBdLen,omitempty"` |
||||
Offset int64 `json:"scsiOffset,omitempty"` |
||||
RPM int64 `json:"sciRpm,omitempty"` |
||||
} |
||||
|
||||
// NvmeInfo contains NVMe drive info
|
||||
type NvmeInfo struct { |
||||
SerialNum string `json:"serialNum,omitempty"` |
||||
VendorID string `json:"vendorId,omitempty"` |
||||
FirmwareVersion string `json:"firmwareVersion,omitempty"` |
||||
ModelNum string `json:"modelNum,omitempty"` |
||||
SpareAvailable string `json:"spareAvailable,omitempty"` |
||||
SpareThreshold string `json:"spareThreshold,omitempty"` |
||||
Temperature string `json:"temperature,omitempty"` |
||||
CriticalWarning string `json:"criticalWarning,omitempty"` |
||||
|
||||
MaxDataTransferPages int `json:"maxDataTransferPages,omitempty"` |
||||
ControllerBusyTime *big.Int `json:"controllerBusyTime,omitempty"` |
||||
PowerOnHours *big.Int `json:"powerOnHours,omitempty"` |
||||
PowerCycles *big.Int `json:"powerCycles,omitempty"` |
||||
UnsafeShutdowns *big.Int `json:"unsafeShutdowns,omitempty"` |
||||
MediaAndDataIntegrityErrors *big.Int `json:"mediaAndDataIntgerityErrors,omitempty"` |
||||
DataUnitsReadBytes *big.Int `json:"dataUnitsReadBytes,omitempty"` |
||||
DataUnitsWrittenBytes *big.Int `json:"dataUnitsWrittenBytes,omitempty"` |
||||
HostReadCommands *big.Int `json:"hostReadCommands,omitempty"` |
||||
HostWriteCommands *big.Int `json:"hostWriteCommands,omitempty"` |
||||
} |
Loading…
Reference in new issue