receiver/awsebsnvmereceiver/internal/nvme/ebs_metrics_unix.go (87 lines of code) (raw):

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT // The following code is based on https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/pkg/metrics/nvme.go // Copyright 2024 The Kubernetes Authors. // // 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. //go:build linux package nvme import ( "bytes" "encoding/binary" "errors" "fmt" "math" "os" "syscall" "unsafe" ) // As defined in <linux/nvme_ioctl.h>. 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 } var ( ErrInvalidEBSMagic = errors.New("invalid EBS magic number") ErrParseLogPage = errors.New("failed to parse log page") ) func GetMetrics(devicePath string) (EBSMetrics, error) { data, err := getNVMEMetrics(devicePath) if err != nil { return EBSMetrics{}, err } return parseLogPage(data) } // getNVMEMetrics retrieves NVMe metrics by reading the log page from the NVMe device at the given path. func getNVMEMetrics(devicePath string) ([]byte, error) { f, err := os.OpenFile(devicePath, os.O_RDWR, 0) if err != nil { return nil, fmt.Errorf("getNVMEMetrics: error opening device: %w", err) } defer f.Close() data, err := nvmeReadLogPage(f.Fd(), 0xD0) if err != nil { return nil, fmt.Errorf("getNVMEMetrics: error reading log page %w", err) } return data, nil } // nvmeReadLogPage reads an NVMe log page via an ioctl system call. func nvmeReadLogPage(fd uintptr, logID uint8) ([]byte, error) { data := make([]byte, 4096) // 4096 bytes is the length of the log page. bufferLen := len(data) if bufferLen > math.MaxUint32 { return nil, errors.New("nvmeReadLogPage: bufferLen exceeds MaxUint32") } cmd := nvmePassthruCommand{ opcode: 0x02, addr: uint64(uintptr(unsafe.Pointer(&data[0]))), nsid: 1, dataLen: uint32(bufferLen), cdw10: uint32(logID) | (1024 << 16), } status, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, 0xC0484E41, uintptr(unsafe.Pointer(&cmd))) if errno != 0 { return nil, fmt.Errorf("nvmeReadLogPage: ioctl error %w", errno) } if status != 0 { return nil, fmt.Errorf("nvmeReadLogPage: ioctl command failed with status %d", status) } return data, nil } // parseLogPage parses the binary data from an EBS log page into EBSMetrics. func parseLogPage(data []byte) (EBSMetrics, error) { var metrics EBSMetrics reader := bytes.NewReader(data) if err := binary.Read(reader, binary.LittleEndian, &metrics); err != nil { return EBSMetrics{}, fmt.Errorf("%w: %w", ErrParseLogPage, err) } if metrics.EBSMagic != 0x3C23B510 { return EBSMetrics{}, fmt.Errorf("%w: %x", ErrInvalidEBSMagic, metrics.EBSMagic) } return metrics, nil }