Repository: prometheus/procfs
Pull request #634 (2024-04-16 16:25:45)

Update class_thermal.go to ignore only invalid thermal zones which raise fs.PathError
Sum-up: ignore invalid thermal zones to avoid discharding of all thermal zone metric

Some thermal zones generates errors fs.PathError causing the lost of all metric data also for valid thermal zone.

Example of error log: ts=2024-04-10T14:23:57.064Z caller=collector.go:169 level=error msg="collector failed" name=thermal_zone duration_seconds=0.053034792 err="read /sys/class/thermal/thermal_zone24/temp: invalid argument"


Patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index a7a40c1..b36f71c 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -33,6 +33,6 @@ jobs:
         run: sudo apt-get update && sudo apt-get -y install libsnmp-dev
         if: github.repository == 'prometheus/snmp_exporter'
       - name: Lint
-        uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0
+        uses: golangci/golangci-lint-action@9d1e0624a798bb64f6c3cea93db47765312263dc # v5.1.0
         with:
           version: v1.56.2
diff --git a/Makefile.common b/Makefile.common
index 0acfb9d..0e9ace2 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -55,7 +55,7 @@ ifneq ($(shell command -v gotestsum 2> /dev/null),)
 endif
 endif
 
-PROMU_VERSION ?= 0.15.0
+PROMU_VERSION ?= 0.17.0
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
 
 SKIP_GOLANGCI_LINT :=
diff --git a/buddyinfo.go b/buddyinfo.go
index eb88d78..8380750 100644
--- a/buddyinfo.go
+++ b/buddyinfo.go
@@ -58,8 +58,8 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
 			return nil, fmt.Errorf("%w: Invalid number of fields, found: %v", ErrFileParse, parts)
 		}
 
-		node := strings.TrimRight(parts[1], ",")
-		zone := strings.TrimRight(parts[3], ",")
+		node := strings.TrimSuffix(parts[1], ",")
+		zone := strings.TrimSuffix(parts[3], ",")
 		arraySize := len(parts[4:])
 
 		if bucketCount == -1 {
diff --git a/mdstat.go b/mdstat.go
index dd2b898..67a9d2b 100644
--- a/mdstat.go
+++ b/mdstat.go
@@ -23,7 +23,7 @@ import (
 
 var (
 	statusLineRE         = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[([U_]+)\]`)
-	recoveryLineBlocksRE = regexp.MustCompile(`\((\d+)/\d+\)`)
+	recoveryLineBlocksRE = regexp.MustCompile(`\((\d+/\d+)\)`)
 	recoveryLinePctRE    = regexp.MustCompile(`= (.+)%`)
 	recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`)
 	recoveryLineSpeedRE  = regexp.MustCompile(`speed=(.+)[A-Z]`)
@@ -50,6 +50,8 @@ type MDStat struct {
 	BlocksTotal int64
 	// Number of blocks on the device that are in sync.
 	BlocksSynced int64
+	// Number of blocks on the device that need to be synced.
+	BlocksToBeSynced int64
 	// progress percentage of current sync
 	BlocksSyncedPct float64
 	// estimated finishing time for current sync (in minutes)
@@ -115,7 +117,8 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 
 		// If device is syncing at the moment, get the number of currently
 		// synced bytes, otherwise that number equals the size of the device.
-		syncedBlocks := size
+		blocksSynced := size
+		blocksToBeSynced := size
 		speed := float64(0)
 		finish := float64(0)
 		pct := float64(0)
@@ -136,9 +139,9 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 			// Handle case when resync=PENDING or resync=DELAYED.
 			if strings.Contains(lines[syncLineIdx], "PENDING") ||
 				strings.Contains(lines[syncLineIdx], "DELAYED") {
-				syncedBlocks = 0
+				blocksSynced = 0
 			} else {
-				syncedBlocks, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
+				blocksSynced, blocksToBeSynced, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
 				if err != nil {
 					return nil, fmt.Errorf("%w: Cannot parse sync line in md device: %q: %w", ErrFileParse, mdName, err)
 				}
@@ -154,7 +157,8 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
 			DisksSpare:             spare,
 			DisksTotal:             total,
 			BlocksTotal:            size,
-			BlocksSynced:           syncedBlocks,
+			BlocksSynced:           blocksSynced,
+			BlocksToBeSynced:       blocksToBeSynced,
 			BlocksSyncedPct:        pct,
 			BlocksSyncedFinishTime: finish,
 			BlocksSyncedSpeed:      speed,
@@ -206,48 +210,54 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, down, size in
 	return active, total, down, size, nil
 }
 
-func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, finish float64, speed float64, err error) {
+func evalRecoveryLine(recoveryLine string) (blocksSynced int64, blocksToBeSynced int64, pct float64, finish float64, speed float64, err error) {
 	matches := recoveryLineBlocksRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine %s: %w", ErrFileParse, recoveryLine, err)
+		return 0, 0, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine blocks %s: %w", ErrFileParse, recoveryLine, err)
 	}
 
-	syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
+	blocks := strings.Split(matches[1], "/")
+	blocksSynced, err = strconv.ParseInt(blocks[0], 10, 64)
 	if err != nil {
-		return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected parsing of recoveryLine %q: %w", ErrFileParse, recoveryLine, err)
+		return 0, 0, 0, 0, 0, fmt.Errorf("%w: Unable to parse recovery blocks synced %q: %w", ErrFileParse, matches[1], err)
+	}
+
+	blocksToBeSynced, err = strconv.ParseInt(blocks[1], 10, 64)
+	if err != nil {
+		return blocksSynced, 0, 0, 0, 0, fmt.Errorf("%w: Unable to parse recovery to be synced blocks %q: %w", ErrFileParse, matches[2], err)
 	}
 
 	// Get percentage complete
 	matches = recoveryLinePctRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return syncedBlocks, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching percentage %s", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching percentage %s", ErrFileParse, recoveryLine)
 	}
 	pct, err = strconv.ParseFloat(strings.TrimSpace(matches[1]), 64)
 	if err != nil {
-		return syncedBlocks, 0, 0, 0, fmt.Errorf("%w: Error parsing float from recoveryLine %q", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, 0, 0, 0, fmt.Errorf("%w: Error parsing float from recoveryLine %q", ErrFileParse, recoveryLine)
 	}
 
 	// Get time expected left to complete
 	matches = recoveryLineFinishRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return syncedBlocks, pct, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching est. finish time: %s", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, pct, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching est. finish time: %s", ErrFileParse, recoveryLine)
 	}
 	finish, err = strconv.ParseFloat(matches[1], 64)
 	if err != nil {
-		return syncedBlocks, pct, 0, 0, fmt.Errorf("%w: Unable to parse float from recoveryLine: %q", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, pct, 0, 0, fmt.Errorf("%w: Unable to parse float from recoveryLine: %q", ErrFileParse, recoveryLine)
 	}
 
 	// Get recovery speed
 	matches = recoveryLineSpeedRE.FindStringSubmatch(recoveryLine)
 	if len(matches) != 2 {
-		return syncedBlocks, pct, finish, 0, fmt.Errorf("%w: Unexpected recoveryLine value: %s", ErrFileParse, recoveryLine)
+		return blocksSynced, blocksToBeSynced, pct, finish, 0, fmt.Errorf("%w: Unexpected recoveryLine value: %s", ErrFileParse, recoveryLine)
 	}
 	speed, err = strconv.ParseFloat(matches[1], 64)
 	if err != nil {
-		return syncedBlocks, pct, finish, 0, fmt.Errorf("%w: Error parsing float from recoveryLine: %q: %w", ErrFileParse, recoveryLine, err)
+		return blocksSynced, blocksToBeSynced, pct, finish, 0, fmt.Errorf("%w: Error parsing float from recoveryLine: %q: %w", ErrFileParse, recoveryLine, err)
 	}
 
-	return syncedBlocks, pct, finish, speed, nil
+	return blocksSynced, blocksToBeSynced, pct, finish, speed, nil
 }
 
 func evalComponentDevices(deviceFields []string) []string {
diff --git a/mountstats.go b/mountstats.go
index 2f54e77..75a3b6c 100644
--- a/mountstats.go
+++ b/mountstats.go
@@ -88,7 +88,7 @@ type MountStatsNFS struct {
 	// Statistics broken down by filesystem operation.
 	Operations []NFSOperationStats
 	// Statistics about the NFS RPC transport.
-	Transport NFSTransportStats
+	Transport []NFSTransportStats
 }
 
 // mountStats implements MountStats.
@@ -432,7 +432,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
 				return nil, err
 			}
 
-			stats.Transport = *tstats
+			stats.Transport = append(stats.Transport, *tstats)
 		}
 
 		// When encountering "per-operation statistics", we must break this
diff --git a/net_ip_socket.go b/net_ip_socket.go
index 679a91e..b70f1fc 100644
--- a/net_ip_socket.go
+++ b/net_ip_socket.go
@@ -14,14 +14,26 @@
 package procfs
 
 import (
+	"bufio"
 	"encoding/hex"
 	"fmt"
+	"io"
 	"net"
 	"os"
 	"strconv"
 	"strings"
 )
 
+const (
+	// readLimit is used by io.LimitReader while reading the content of the
+	// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
+	// as each line represents a single used socket.
+	// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
+	// With e.g. 150 Byte per line and the maximum number of 65535,
+	// the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP.
+	readLimit = 4294967296 // Byte -> 4 GiB
+)
+
 // This contains generic data structures for both udp and tcp sockets.
 type (
 	// NetIPSocket represents the contents of /proc/net/{t,u}dp{,6} file without the header.
@@ -62,50 +74,49 @@ type (
 )
 
 func newNetIPSocket(file string) (NetIPSocket, error) {
-	var netIPSocket NetIPSocket
-	isUDP := strings.Contains(file, "udp")
-	content, err := os.ReadFile(file)
+	f, err := os.Open(file)
 	if err != nil {
 		return nil, err
 	}
-	lines := strings.Split(string(content), "\n")
-	if len(lines) < 1 {
-		return nil, ErrFileParse
-	}
+	defer f.Close()
 
-	for _, line := range lines[1:] {
-		fields := strings.Fields(line)
-		if len(fields) == 0 {
-			continue
-		}
+	var netIPSocket NetIPSocket
+	isUDP := strings.Contains(file, "udp")
+
+	lr := io.LimitReader(f, readLimit)
+	s := bufio.NewScanner(lr)
+	s.Scan() // skip first line with headers
+	for s.Scan() {
+		fields := strings.Fields(s.Text())
 		line, err := parseNetIPSocketLine(fields, isUDP)
 		if err != nil {
 			return nil, err
 		}
 		netIPSocket = append(netIPSocket, line)
 	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
 	return netIPSocket, nil
 }
 
 // newNetIPSocketSummary creates a new NetIPSocket{,6} from the contents of the given file.
 func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) {
-	var netIPSocketSummary NetIPSocketSummary
-	var udpPacketDrops uint64
-	isUDP := strings.Contains(file, "udp")
-	content, err := os.ReadFile(file)
+	f, err := os.Open(file)
 	if err != nil {
 		return nil, err
 	}
-	lines := strings.Split(string(content), "\n")
-	if len(lines) < 1 {
-		return nil, ErrFileParse
-	}
+	defer f.Close()
 
-	for _, line := range lines[1:] {
-		fields := strings.Fields(line)
-		if len(fields) == 0 {
-			continue
-		}
+	var netIPSocketSummary NetIPSocketSummary
+	var udpPacketDrops uint64
+	isUDP := strings.Contains(file, "udp")
+
+	lr := io.LimitReader(f, readLimit)
+	s := bufio.NewScanner(lr)
+	s.Scan() // skip first line with headers
+	for s.Scan() {
+		fields := strings.Fields(s.Text())
 		line, err := parseNetIPSocketLine(fields, isUDP)
 		if err != nil {
 			return nil, err
@@ -118,6 +129,9 @@ func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) {
 			netIPSocketSummary.Drops = &udpPacketDrops
 		}
 	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
 	return &netIPSocketSummary, nil
 }
 
diff --git a/proc.go b/proc.go
index 0e8c4fa..1427963 100644
--- a/proc.go
+++ b/proc.go
@@ -137,7 +137,7 @@ func (p Proc) CmdLine() ([]string, error) {
 		return []string{}, nil
 	}
 
-	return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil
+	return strings.Split(string(bytes.TrimRight(data, "\x00")), "\x00"), nil
 }
 
 // Wchan returns the wchan (wait channel) of a process.
diff --git a/proc_smaps.go b/proc_smaps.go
index ad8785a..09060e8 100644
--- a/proc_smaps.go
+++ b/proc_smaps.go
@@ -127,7 +127,7 @@ func (s *ProcSMapsRollup) parseLine(line string) error {
 	}
 
 	v := strings.TrimSpace(kv[1])
-	v = strings.TrimRight(v, " kB")
+	v = strings.TrimSuffix(v, " kB")
 
 	vKBytes, err := strconv.ParseUint(v, 10, 64)
 	if err != nil {
diff --git a/sysfs/class_fibrechannel.go b/sysfs/class_fibrechannel.go
index ded07a8..4b583f7 100644
--- a/sysfs/class_fibrechannel.go
+++ b/sysfs/class_fibrechannel.go
@@ -28,36 +28,36 @@ import (
 const fibrechannelClassPath = "class/fc_host"
 
 type FibreChannelCounters struct {
-	DumpedFrames          uint64 // /sys/class/fc_host/<Name>/statistics/dumped_frames
-	ErrorFrames           uint64 // /sys/class/fc_host/<Name>/statistics/error_frames
-	InvalidCRCCount       uint64 // /sys/class/fc_host/<Name>/statistics/invalid_crc_count
-	RXFrames              uint64 // /sys/class/fc_host/<Name>/statistics/rx_frames
-	RXWords               uint64 // /sys/class/fc_host/<Name>/statistics/rx_words
-	TXFrames              uint64 // /sys/class/fc_host/<Name>/statistics/tx_frames
-	TXWords               uint64 // /sys/class/fc_host/<Name>/statistics/tx_words
-	SecondsSinceLastReset uint64 // /sys/class/fc_host/<Name>/statistics/seconds_since_last_reset
-	InvalidTXWordCount    uint64 // /sys/class/fc_host/<Name>/statistics/invalid_tx_word_count
-	LinkFailureCount      uint64 // /sys/class/fc_host/<Name>/statistics/link_failure_count
-	LossOfSyncCount       uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_sync_count
-	LossOfSignalCount     uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_signal_count
-	NosCount              uint64 // /sys/class/fc_host/<Name>/statistics/nos_count
-	FCPPacketAborts       uint64 // / sys/class/fc_host/<Name>/statistics/fcp_packet_aborts
+	DumpedFrames          *uint64 // /sys/class/fc_host/<Name>/statistics/dumped_frames
+	ErrorFrames           *uint64 // /sys/class/fc_host/<Name>/statistics/error_frames
+	InvalidCRCCount       *uint64 // /sys/class/fc_host/<Name>/statistics/invalid_crc_count
+	RXFrames              *uint64 // /sys/class/fc_host/<Name>/statistics/rx_frames
+	RXWords               *uint64 // /sys/class/fc_host/<Name>/statistics/rx_words
+	TXFrames              *uint64 // /sys/class/fc_host/<Name>/statistics/tx_frames
+	TXWords               *uint64 // /sys/class/fc_host/<Name>/statistics/tx_words
+	SecondsSinceLastReset *uint64 // /sys/class/fc_host/<Name>/statistics/seconds_since_last_reset
+	InvalidTXWordCount    *uint64 // /sys/class/fc_host/<Name>/statistics/invalid_tx_word_count
+	LinkFailureCount      *uint64 // /sys/class/fc_host/<Name>/statistics/link_failure_count
+	LossOfSyncCount       *uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_sync_count
+	LossOfSignalCount     *uint64 // /sys/class/fc_host/<Name>/statistics/loss_of_signal_count
+	NosCount              *uint64 // /sys/class/fc_host/<Name>/statistics/nos_count
+	FCPPacketAborts       *uint64 // /sys/class/fc_host/<Name>/statistics/fcp_packet_aborts
 }
 
 type FibreChannelHost struct {
-	Name             string               // /sys/class/fc_host/<Name>
-	Speed            string               // /sys/class/fc_host/<Name>/speed
-	PortState        string               // /sys/class/fc_host/<Name>/port_state
-	PortType         string               // /sys/class/fc_host/<Name>/port_type
-	SymbolicName     string               // /sys/class/fc_host/<Name>/symbolic_name
-	NodeName         string               // /sys/class/fc_host/<Name>/node_name
-	PortID           string               // /sys/class/fc_host/<Name>/port_id
-	PortName         string               // /sys/class/fc_host/<Name>/port_name
-	FabricName       string               // /sys/class/fc_host/<Name>/fabric_name
-	DevLossTMO       string               // /sys/class/fc_host/<Name>/dev_loss_tmo
-	SupportedClasses string               // /sys/class/fc_host/<Name>/supported_classes
-	SupportedSpeeds  string               // /sys/class/fc_host/<Name>/supported_speeds
-	Counters         FibreChannelCounters // /sys/class/fc_host/<Name>/statistics/*
+	Name             *string               // /sys/class/fc_host/<Name>
+	Speed            *string               // /sys/class/fc_host/<Name>/speed
+	PortState        *string               // /sys/class/fc_host/<Name>/port_state
+	PortType         *string               // /sys/class/fc_host/<Name>/port_type
+	SymbolicName     *string               // /sys/class/fc_host/<Name>/symbolic_name
+	NodeName         *string               // /sys/class/fc_host/<Name>/node_name
+	PortID           *string               // /sys/class/fc_host/<Name>/port_id
+	PortName         *string               // /sys/class/fc_host/<Name>/port_name
+	FabricName       *string               // /sys/class/fc_host/<Name>/fabric_name
+	DevLossTMO       *string               // /sys/class/fc_host/<Name>/dev_loss_tmo
+	SupportedClasses *string               // /sys/class/fc_host/<Name>/supported_classes
+	SupportedSpeeds  *string               // /sys/class/fc_host/<Name>/supported_speeds
+	Counters         *FibreChannelCounters // /sys/class/fc_host/<Name>/statistics/*
 }
 
 type FibreChannelClass map[string]FibreChannelHost
@@ -78,7 +78,7 @@ func (fs FS) FibreChannelClass() (FibreChannelClass, error) {
 			return nil, err
 		}
 
-		fcc[host.Name] = *host
+		fcc[*host.Name] = *host
 	}
 
 	return fcc, nil
@@ -87,7 +87,7 @@ func (fs FS) FibreChannelClass() (FibreChannelClass, error) {
 // Parse a single FC host.
 func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
 	path := fs.sys.Path(fibrechannelClassPath, name)
-	host := FibreChannelHost{Name: name}
+	host := FibreChannelHost{Name: &name}
 
 	for _, f := range [...]string{"speed", "port_state", "port_type", "node_name", "port_id", "port_name", "fabric_name", "dev_loss_tmo", "symbolic_name", "supported_classes", "supported_speeds"} {
 		name := filepath.Join(path, f)
@@ -103,39 +103,39 @@ func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
 
 		switch f {
 		case "speed":
-			host.Speed = value
+			host.Speed = &value
 		case "port_state":
-			host.PortState = value
+			host.PortState = &value
 		case "port_type":
-			host.PortType = value
+			host.PortType = &value
 		case "node_name":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.NodeName = value
+			host.NodeName = &value
 		case "port_id":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.PortID = value
+			host.PortID = &value
 		case "port_name":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.PortName = value
+			host.PortName = &value
 		case "fabric_name":
 			if len(value) > 2 {
 				value = value[2:]
 			}
-			host.FabricName = value
+			host.FabricName = &value
 		case "dev_loss_tmo":
-			host.DevLossTMO = value
+			host.DevLossTMO = &value
 		case "supported_classes":
-			host.SupportedClasses = value
+			host.SupportedClasses = &value
 		case "supported_speeds":
-			host.SupportedSpeeds = value
+			host.SupportedSpeeds = &value
 		case "symbolic_name":
-			host.SymbolicName = value
+			host.SymbolicName = &value
 		}
 	}
 
@@ -143,7 +143,7 @@ func (fs FS) parseFibreChannelHost(name string) (*FibreChannelHost, error) {
 	if err != nil {
 		return nil, err
 	}
-	host.Counters = *counters
+	host.Counters = counters
 
 	return &host, nil
 }
@@ -178,9 +178,9 @@ func parseFibreChannelStatistics(hostPath string) (*FibreChannelCounters, error)
 		// Below switch was automatically generated. Don't need everything in there yet, so the unwanted bits are commented out.
 		switch f.Name() {
 		case "dumped_frames":
-			counters.DumpedFrames = *vp.PUInt64()
+			counters.DumpedFrames = vp.PUInt64()
 		case "error_frames":
-			counters.ErrorFrames = *vp.PUInt64()
+			counters.ErrorFrames = vp.PUInt64()
 		/*
 			case "fc_no_free_exch":
 				counters.FcNoFreeExch = *vp.PUInt64()
@@ -208,41 +208,41 @@ func parseFibreChannelStatistics(hostPath string) (*FibreChannelCounters, error)
 				counters.FcpOutputRequests = *vp.PUInt64()
 		*/
 		case "fcp_packet_aborts":
-			counters.FCPPacketAborts = *vp.PUInt64()
+			counters.FCPPacketAborts = vp.PUInt64()
 			/*
 				case "fcp_packet_alloc_failures":
 					counters.FcpPacketAllocFailures = *vp.PUInt64()
 			*/
 		case "invalid_tx_word_count":
-			counters.InvalidTXWordCount = *vp.PUInt64()
+			counters.InvalidTXWordCount = vp.PUInt64()
 		case "invalid_crc_count":
-			counters.InvalidCRCCount = *vp.PUInt64()
+			counters.InvalidCRCCount = vp.PUInt64()
 		case "link_failure_count":
-			counters.LinkFailureCount = *vp.PUInt64()
+			counters.LinkFailureCount = vp.PUInt64()
 		/*
 			case "lip_count":
 					counters.LipCount = *vp.PUInt64()
 		*/
 		case "loss_of_signal_count":
-			counters.LossOfSignalCount = *vp.PUInt64()
+			counters.LossOfSignalCount = vp.PUInt64()
 		case "loss_of_sync_count":
-			counters.LossOfSyncCount = *vp.PUInt64()
+			counters.LossOfSyncCount = vp.PUInt64()
 		case "nos_count":
-			counters.NosCount = *vp.PUInt64()
+			counters.NosCount = vp.PUInt64()
 		/*
 			case "prim_seq_protocol_err_count":
 				counters.PrimSeqProtocolErrCount = *vp.PUInt64()
 		*/
 		case "rx_frames":
-			counters.RXFrames = *vp.PUInt64()
+			counters.RXFrames = vp.PUInt64()
 		case "rx_words":
-			counters.RXWords = *vp.PUInt64()
+			counters.RXWords = vp.PUInt64()
 		case "seconds_since_last_reset":
-			counters.SecondsSinceLastReset = *vp.PUInt64()
+			counters.SecondsSinceLastReset = vp.PUInt64()
 		case "tx_frames":
-			counters.TXFrames = *vp.PUInt64()
+			counters.TXFrames = vp.PUInt64()
 		case "tx_words":
-			counters.TXWords = *vp.PUInt64()
+			counters.TXWords = vp.PUInt64()
 		}
 
 		if err := vp.Err(); err != nil {
diff --git a/sysfs/class_thermal.go b/sysfs/class_thermal.go
index 0935a21..ab9048f 100644
--- a/sysfs/class_thermal.go
+++ b/sysfs/class_thermal.go
@@ -24,6 +24,7 @@ import (
 	"syscall"
 
 	"github.com/prometheus/procfs/internal/util"
+	fsp "io/fs"
 )
 
 // ClassThermalZoneStats contains info from files in /sys/class/thermal/thermal_zone<zone>
@@ -49,7 +50,7 @@ func (fs FS) ClassThermalZoneStats() ([]ClassThermalZoneStats, error) {
 	for _, zone := range zones {
 		zoneStats, err := parseClassThermalZone(zone)
 		if err != nil {
-			if errors.Is(err, syscall.ENODATA) {
+			if errors.Is(err, syscall.ENODATA) || errors.As(err, new(*fsp.PathError)) {
 				continue
 			}
 			return nil, err
diff --git a/sysfs/system_cpu.go b/sysfs/system_cpu.go
index fbecc19..5b1bec8 100644
--- a/sysfs/system_cpu.go
+++ b/sysfs/system_cpu.go
@@ -65,6 +65,10 @@ type SystemCPUCpufreqStats struct {
 	Governor                 string
 	RelatedCpus              string
 	SetSpeed                 string
+	// Refer `CONFIG_CPU_FREQ_STAT`: https://www.kernel.org/doc/html/latest/cpu-freq/cpufreq-stats.html#configuring-cpufreq-stats
+	CpuinfoFrequencyDuration         *map[uint64]uint64
+	CpuinfoFrequencyTransitionsTotal *uint64
+	CpuinfoTransitionTable           *[][]uint64
 }
 
 // CPUs returns a slice of all CPUs in `/sys/devices/system/cpu`.
@@ -292,19 +296,93 @@ func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) {
 		}
 	}
 
+	// "total_trans" is the total number of times the CPU has changed frequency.
+	var cpuinfoFrequencyTransitionsTotal *uint64
+	cpuinfoFrequencyTransitionsTotalUint, err := util.ReadUintFromFile(filepath.Join(cpuPath, "stats", "total_trans"))
+	if err != nil {
+		if !(os.IsNotExist(err) || os.IsPermission(err)) {
+			return &SystemCPUCpufreqStats{}, err
+		}
+	} else {
+		cpuinfoFrequencyTransitionsTotal = &cpuinfoFrequencyTransitionsTotalUint
+	}
+
+	// "time_in_state" is the total time spent at each frequency.
+	var cpuinfoFrequencyDuration *map[uint64]uint64
+	cpuinfoFrequencyDurationString, err := util.ReadFileNoStat(filepath.Join(cpuPath, "stats", "time_in_state"))
+	if err != nil {
+		if !(os.IsNotExist(err) || os.IsPermission(err)) {
+			return &SystemCPUCpufreqStats{}, err
+		}
+	} else {
+		cpuinfoFrequencyDuration = &map[uint64]uint64{}
+		for _, line := range strings.Split(string(cpuinfoFrequencyDurationString), "\n") {
+			if line == "" {
+				continue
+			}
+			fields := strings.Fields(line)
+			if len(fields) != 2 {
+				return &SystemCPUCpufreqStats{}, fmt.Errorf("unexpected number of fields in time_in_state: %v", fields)
+			}
+			freq, err := strconv.ParseUint(fields[0], 10, 64)
+			if err != nil {
+				return &SystemCPUCpufreqStats{}, err
+			}
+			duration, err := strconv.ParseUint(fields[1], 10, 64)
+			if err != nil {
+				return &SystemCPUCpufreqStats{}, err
+			}
+			(*cpuinfoFrequencyDuration)[freq] = duration
+		}
+	}
+
+	// "trans_table" contains information about all the CPU frequency transitions.
+	var cpuinfoTransitionTable *[][]uint64
+	cpuinfoTransitionTableString, err := util.ReadFileNoStat(filepath.Join(cpuPath, "stats", "trans_table"))
+	if err != nil {
+		if !(os.IsNotExist(err) || os.IsPermission(err)) {
+			return &SystemCPUCpufreqStats{}, err
+		}
+	} else {
+		cpuinfoTransitionTable = &[][]uint64{}
+		for i, line := range strings.Split(string(cpuinfoTransitionTableString), "\n") {
+			// Skip the "From: To" header.
+			if i == 0 || line == "" {
+				continue
+			}
+			fields := strings.Fields(line)
+			fields[0] = strings.TrimSuffix(fields[0], ":")
+			cpuinfoTransitionTableRow := make([]uint64, len(fields))
+			for i := range fields {
+				if len(fields[i]) == 0 {
+					continue
+				}
+				f, err := strconv.ParseUint(fields[i], 10, 64)
+				if err != nil {
+					return &SystemCPUCpufreqStats{}, err
+				}
+				cpuinfoTransitionTableRow[i] = f
+			}
+			*cpuinfoTransitionTable = append(*cpuinfoTransitionTable, cpuinfoTransitionTableRow)
+		}
+	}
+
 	return &SystemCPUCpufreqStats{
-		CpuinfoCurrentFrequency:  uintOut[0],
-		CpuinfoMaximumFrequency:  uintOut[1],
-		CpuinfoMinimumFrequency:  uintOut[2],
-		CpuinfoTransitionLatency: uintOut[3],
-		ScalingCurrentFrequency:  uintOut[4],
-		ScalingMaximumFrequency:  uintOut[5],
-		ScalingMinimumFrequency:  uintOut[6],
-		AvailableGovernors:       stringOut[0],
-		Driver:                   stringOut[1],
-		Governor:                 stringOut[2],
-		RelatedCpus:              stringOut[3],
-		SetSpeed:                 stringOut[4],
+		CpuinfoCurrentFrequency:          uintOut[0],
+		CpuinfoMaximumFrequency:          uintOut[1],
+		CpuinfoMinimumFrequency:          uintOut[2],
+		CpuinfoTransitionLatency:         uintOut[3],
+		ScalingCurrentFrequency:          uintOut[4],
+		ScalingMaximumFrequency:          uintOut[5],
+		ScalingMinimumFrequency:          uintOut[6],
+		AvailableGovernors:               stringOut[0],
+		Driver:                           stringOut[1],
+		Governor:                         stringOut[2],
+		RelatedCpus:                      stringOut[3],
+		SetSpeed:                         stringOut[4],
+		CpuinfoFrequencyDuration:         cpuinfoFrequencyDuration,
+		CpuinfoFrequencyTransitionsTotal: cpuinfoFrequencyTransitionsTotal,
+		CpuinfoTransitionTable:           cpuinfoTransitionTable,
 	}, nil
 }
 
@@ -321,7 +399,7 @@ func parseCPURange(data []byte) ([]uint16, error) {
 
 	var cpusInt = []uint16{}
 
-	for _, cpu := range strings.Split(strings.TrimRight(string(data), "\n"), ",") {
+	for _, cpu := range strings.Split(strings.TrimSuffix(string(data), "\n"), ",") {
 		if cpu == "" {
 			continue
 		}


Test patch:
diff --git a/mdstat_test.go b/mdstat_test.go
index 871e95a..adeb2bb 100644
--- a/mdstat_test.go
+++ b/mdstat_test.go
@@ -25,23 +25,261 @@ func TestFS_MDStat(t *testing.T) {
 	}
 
 	refs := map[string]MDStat{
-		"md127": {Name: "md127", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 312319552, BlocksSynced: 312319552, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdi2", "sdj2"}},
-		"md0":   {Name: "md0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 248896, BlocksSynced: 248896, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdi1", "sdj1"}},
-		"md4":   {Name: "md4", ActivityState: "inactive", DisksActive: 0, DisksTotal: 0, DisksFailed: 1, DisksDown: 0, DisksSpare: 1, BlocksTotal: 4883648, BlocksSynced: 4883648, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda3", "sdb3"}},
-		"md6":   {Name: "md6", ActivityState: "recovering", DisksActive: 1, DisksTotal: 2, DisksFailed: 1, DisksDown: 1, DisksSpare: 1, BlocksTotal: 195310144, BlocksSynced: 16775552, BlocksSyncedPct: 8.5, BlocksSyncedFinishTime: 17, BlocksSyncedSpeed: 259783, Devices: []string{"sdb2", "sdc", "sda2"}},
-		"md3":   {Name: "md3", ActivityState: "active", DisksActive: 8, DisksTotal: 8, DisksFailed: 0, DisksDown: 0, DisksSpare: 2, BlocksTotal: 5853468288, BlocksSynced: 5853468288, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}},
-		"md8":   {Name: "md8", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 2, BlocksTotal: 195310144, BlocksSynced: 16775552, BlocksSyncedPct: 8.5, BlocksSyncedFinishTime: 17, BlocksSyncedSpeed: 259783, Devices: []string{"sdb1", "sda1", "sdc", "sde"}},
-		"md7":   {Name: "md7", ActivityState: "active", DisksActive: 3, DisksTotal: 4, DisksFailed: 1, DisksDown: 1, DisksSpare: 0, BlocksTotal: 7813735424, BlocksSynced: 7813735424, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb1", "sde1", "sdd1", "sdc1"}},
-		"md9":   {Name: "md9", ActivityState: "resyncing", DisksActive: 4, DisksTotal: 4, DisksSpare: 1, DisksDown: 0, DisksFailed: 2, BlocksTotal: 523968, BlocksSynced: 0, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}},
-		"md10":  {Name: "md10", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 314159265, BlocksSynced: 314159265, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda1", "sdb1"}},
-		"md11":  {Name: "md11", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, DisksFailed: 1, DisksDown: 0, DisksSpare: 2, BlocksTotal: 4190208, BlocksSynced: 0, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}},
-		"md12":  {Name: "md12", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksSpare: 0, DisksDown: 0, DisksFailed: 0, BlocksTotal: 3886394368, BlocksSynced: 3886394368, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdc2", "sdd2"}},
-		"md120": {Name: "md120", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 2095104, BlocksSynced: 2095104, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sda1", "sdb1"}},
-		"md126": {Name: "md126", ActivityState: "active", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 1855870976, BlocksSynced: 1855870976, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb", "sdc"}},
-		"md219": {Name: "md219", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, DisksActive: 0, DisksDown: 0, DisksSpare: 3, BlocksTotal: 7932, BlocksSynced: 7932, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdc", "sda"}},
-		"md00":  {Name: "md00", ActivityState: "active", DisksActive: 1, DisksTotal: 1, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 4186624, BlocksSynced: 4186624, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"xvdb"}},
-		"md101": {Name: "md101", ActivityState: "active", DisksActive: 3, DisksTotal: 3, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 322560, BlocksSynced: 322560, BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, Devices: []string{"sdb", "sdd", "sdc"}},
-		"md201": {Name: "md201", ActivityState: "checking", DisksActive: 2, DisksTotal: 2, DisksFailed: 0, DisksDown: 0, DisksSpare: 0, BlocksTotal: 1993728, BlocksSynced: 114176, BlocksSyncedPct: 5.7, BlocksSyncedFinishTime: 0.2, BlocksSyncedSpeed: 114176, Devices: []string{"sda3", "sdb3"}},
+		"md127": {
+			Name:                   "md127",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            312319552,
+			BlocksSynced:           312319552,
+			BlocksToBeSynced:       312319552,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdi2", "sdj2"}},
+		"md0": {
+			Name:                   "md0",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            248896,
+			BlocksSynced:           248896,
+			BlocksToBeSynced:       248896,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdi1", "sdj1"}},
+		"md4": {
+			Name:                   "md4",
+			ActivityState:          "inactive",
+			DisksActive:            0,
+			DisksTotal:             0,
+			DisksFailed:            1,
+			DisksDown:              0,
+			DisksSpare:             1,
+			BlocksTotal:            4883648,
+			BlocksSynced:           4883648,
+			BlocksToBeSynced:       4883648,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda3", "sdb3"}},
+		"md6": {
+			Name:                   "md6",
+			ActivityState:          "recovering",
+			DisksActive:            1,
+			DisksTotal:             2,
+			DisksFailed:            1,
+			DisksDown:              1,
+			DisksSpare:             1,
+			BlocksTotal:            195310144,
+			BlocksSynced:           16775552,
+			BlocksToBeSynced:       195310144,
+			BlocksSyncedPct:        8.5,
+			BlocksSyncedFinishTime: 17,
+			BlocksSyncedSpeed:      259783,
+			Devices:                []string{"sdb2", "sdc", "sda2"}},
+		"md3": {
+			Name:                   "md3",
+			ActivityState:          "active",
+			DisksActive:            8,
+			DisksTotal:             8,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             2,
+			BlocksTotal:            5853468288,
+			BlocksSynced:           5853468288,
+			BlocksToBeSynced:       5853468288,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}},
+		"md8": {
+			Name:                   "md8",
+			ActivityState:          "resyncing",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             2,
+			BlocksTotal:            195310144,
+			BlocksSynced:           16775552,
+			BlocksToBeSynced:       195310144,
+			BlocksSyncedPct:        8.5,
+			BlocksSyncedFinishTime: 17,
+			BlocksSyncedSpeed:      259783,
+			Devices:                []string{"sdb1", "sda1", "sdc", "sde"}},
+		"md7": {
+			Name:                   "md7",
+			ActivityState:          "active",
+			DisksActive:            3,
+			DisksTotal:             4,
+			DisksFailed:            1,
+			DisksDown:              1,
+			DisksSpare:             0,
+			BlocksTotal:            7813735424,
+			BlocksSynced:           7813735424,
+			BlocksToBeSynced:       7813735424,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb1", "sde1", "sdd1", "sdc1"}},
+		"md9": {
+			Name:                   "md9",
+			ActivityState:          "resyncing",
+			DisksActive:            4,
+			DisksTotal:             4,
+			DisksSpare:             1,
+			DisksDown:              0,
+			DisksFailed:            2,
+			BlocksTotal:            523968,
+			BlocksSynced:           0,
+			BlocksToBeSynced:       523968,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}},
+		"md10": {
+			Name:                   "md10",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            314159265,
+			BlocksSynced:           314159265,
+			BlocksToBeSynced:       314159265,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda1", "sdb1"}},
+		"md11": {
+			Name:                   "md11",
+			ActivityState:          "resyncing",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            1,
+			DisksDown:              0,
+			DisksSpare:             2,
+			BlocksTotal:            4190208,
+			BlocksSynced:           0,
+			BlocksToBeSynced:       4190208,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}},
+		"md12": {
+			Name:                   "md12",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksSpare:             0,
+			DisksDown:              0,
+			DisksFailed:            0,
+			BlocksTotal:            3886394368,
+			BlocksSynced:           3886394368,
+			BlocksToBeSynced:       3886394368,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdc2", "sdd2"}},
+		"md120": {
+			Name:                   "md120",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            2095104,
+			BlocksSynced:           2095104,
+			BlocksToBeSynced:       2095104,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sda1", "sdb1"}},
+		"md126": {
+			Name:                   "md126",
+			ActivityState:          "active",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            1855870976,
+			BlocksSynced:           1855870976,
+			BlocksToBeSynced:       1855870976,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb", "sdc"}},
+		"md219": {
+			Name:                   "md219",
+			ActivityState:          "inactive",
+			DisksTotal:             0,
+			DisksFailed:            0,
+			DisksActive:            0,
+			DisksDown:              0,
+			DisksSpare:             3,
+			BlocksTotal:            7932,
+			BlocksSynced:           7932,
+			BlocksToBeSynced:       7932,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdc", "sda"}},
+		"md00": {
+			Name:                   "md00",
+			ActivityState:          "active",
+			DisksActive:            1,
+			DisksTotal:             1,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            4186624,
+			BlocksSynced:           4186624,
+			BlocksToBeSynced:       4186624,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"xvdb"}},
+		"md101": {
+			Name:                   "md101",
+			ActivityState:          "active",
+			DisksActive:            3,
+			DisksTotal:             3,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            322560,
+			BlocksSynced:           322560,
+			BlocksToBeSynced:       322560,
+			BlocksSyncedPct:        0,
+			BlocksSyncedFinishTime: 0,
+			BlocksSyncedSpeed:      0,
+			Devices:                []string{"sdb", "sdd", "sdc"}},
+		"md201": {
+			Name:                   "md201",
+			ActivityState:          "checking",
+			DisksActive:            2,
+			DisksTotal:             2,
+			DisksFailed:            0,
+			DisksDown:              0,
+			DisksSpare:             0,
+			BlocksTotal:            1993728,
+			BlocksSynced:           114176,
+			BlocksToBeSynced:       1993728,
+			BlocksSyncedPct:        5.7,
+			BlocksSyncedFinishTime: 0.2,
+			BlocksSyncedSpeed:      114176,
+			Devices:                []string{"sda3", "sdb3"}},
 	}
 
 	if want, have := len(refs), len(mdStats); want != have {
diff --git a/mountstats_test.go b/mountstats_test.go
index f9588f0..262f7d1 100644
--- a/mountstats_test.go
+++ b/mountstats_test.go
@@ -116,21 +116,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.0",
-					Transport: NFSTransportStats{
-						Protocol:                 "tcp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  3,
-						ConnectIdleTime:          4,
-						IdleTimeSeconds:          5,
-						Sends:                    6,
-						Receives:                 7,
-						BadTransactionIDs:        8,
-						CumulativeActiveRequests: 9,
-						CumulativeBacklog:        10,
-						MaximumRPCSlotsUsed:      0, // these three are not
-						CumulativeSendingQueue:   0, // present in statvers=1.0
-						CumulativePendingQueue:   0, //
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "tcp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  3,
+							ConnectIdleTime:          4,
+							IdleTimeSeconds:          5,
+							Sends:                    6,
+							Receives:                 7,
+							BadTransactionIDs:        8,
+							CumulativeActiveRequests: 9,
+							CumulativeBacklog:        10,
+							MaximumRPCSlotsUsed:      0, // these three are not
+							CumulativeSendingQueue:   0, // present in statvers=1.0
+							CumulativePendingQueue:   0, //
+						},
 					},
 				},
 			}},
@@ -144,21 +146,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.0",
-					Transport: NFSTransportStats{
-						Protocol:                 "udp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  0,
-						ConnectIdleTime:          0,
-						IdleTimeSeconds:          0,
-						Sends:                    3,
-						Receives:                 4,
-						BadTransactionIDs:        5,
-						CumulativeActiveRequests: 6,
-						CumulativeBacklog:        7,
-						MaximumRPCSlotsUsed:      0, // these three are not
-						CumulativeSendingQueue:   0, // present in statvers=1.0
-						CumulativePendingQueue:   0, //
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "udp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  0,
+							ConnectIdleTime:          0,
+							IdleTimeSeconds:          0,
+							Sends:                    3,
+							Receives:                 4,
+							BadTransactionIDs:        5,
+							CumulativeActiveRequests: 6,
+							CumulativeBacklog:        7,
+							MaximumRPCSlotsUsed:      0, // these three are not
+							CumulativeSendingQueue:   0, // present in statvers=1.0
+							CumulativePendingQueue:   0, //
+						},
 					},
 				},
 			}},
@@ -172,21 +176,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.1",
-					Transport: NFSTransportStats{
-						Protocol:                 "tcp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  3,
-						ConnectIdleTime:          4,
-						IdleTimeSeconds:          5,
-						Sends:                    6,
-						Receives:                 7,
-						BadTransactionIDs:        8,
-						CumulativeActiveRequests: 9,
-						CumulativeBacklog:        10,
-						MaximumRPCSlotsUsed:      11,
-						CumulativeSendingQueue:   12,
-						CumulativePendingQueue:   13,
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "tcp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  3,
+							ConnectIdleTime:          4,
+							IdleTimeSeconds:          5,
+							Sends:                    6,
+							Receives:                 7,
+							BadTransactionIDs:        8,
+							CumulativeActiveRequests: 9,
+							CumulativeBacklog:        10,
+							MaximumRPCSlotsUsed:      11,
+							CumulativeSendingQueue:   12,
+							CumulativePendingQueue:   13,
+						},
 					},
 				},
 			}},
@@ -200,21 +206,23 @@ func TestMountStats(t *testing.T) {
 				Type:   "nfs",
 				Stats: &MountStatsNFS{
 					StatVersion: "1.1",
-					Transport: NFSTransportStats{
-						Protocol:                 "udp",
-						Port:                     1,
-						Bind:                     2,
-						Connect:                  0, // these three are not
-						ConnectIdleTime:          0, // present for UDP
-						IdleTimeSeconds:          0, //
-						Sends:                    3,
-						Receives:                 4,
-						BadTransactionIDs:        5,
-						CumulativeActiveRequests: 6,
-						CumulativeBacklog:        7,
-						MaximumRPCSlotsUsed:      8,
-						CumulativeSendingQueue:   9,
-						CumulativePendingQueue:   10,
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "udp",
+							Port:                     1,
+							Bind:                     2,
+							Connect:                  0, // these three are not
+							ConnectIdleTime:          0, // present for UDP
+							IdleTimeSeconds:          0, //
+							Sends:                    3,
+							Receives:                 4,
+							BadTransactionIDs:        5,
+							CumulativeActiveRequests: 6,
+							CumulativeBacklog:        7,
+							MaximumRPCSlotsUsed:      8,
+							CumulativeSendingQueue:   9,
+							CumulativePendingQueue:   10,
+						},
 					},
 				},
 			}},
@@ -354,17 +362,19 @@ func TestMountStats(t *testing.T) {
 								CumulativeTotalRequestMilliseconds:  1953587717,
 							},
 						},
-						Transport: NFSTransportStats{
-							Protocol:                 "tcp",
-							Port:                     832,
-							Connect:                  1,
-							IdleTimeSeconds:          11,
-							Sends:                    6428,
-							Receives:                 6428,
-							CumulativeActiveRequests: 12154,
-							MaximumRPCSlotsUsed:      24,
-							CumulativeSendingQueue:   26,
-							CumulativePendingQueue:   5726,
+						Transport: []NFSTransportStats{
+							{
+								Protocol:                 "tcp",
+								Port:                     832,
+								Connect:                  1,
+								IdleTimeSeconds:          11,
+								Sends:                    6428,
+								Receives:                 6428,
+								CumulativeActiveRequests: 12154,
+								MaximumRPCSlotsUsed:      24,
+								CumulativeSendingQueue:   26,
+								CumulativePendingQueue:   5726,
+							},
 						},
 					},
 				},
@@ -428,39 +438,41 @@ func TestMountStats(t *testing.T) {
 							Errors:                              0,
 						},
 					},
-					Transport: NFSTransportStats{
-						Protocol:                 "rdma",
-						Port:                     0,
-						Bind:                     0,
-						Connect:                  5808,
-						ConnectIdleTime:          62,
-						IdleTimeSeconds:          0,
-						Sends:                    494490723,
-						Receives:                 494490687,
-						BadTransactionIDs:        36,
-						CumulativeActiveRequests: 10032963746,
-						CumulativeBacklog:        1282789,
-						MaximumRPCSlotsUsed:      0,
-						CumulativeSendingQueue:   0,
-						CumulativePendingQueue:   0,
-						ReadChunkCount:           107150285,
-						WriteChunkCount:          1226637531,
-						ReplyChunkCount:          2673889,
-						TotalRdmaRequest:         135120843409861,
-						PullupCopyCount:          135119397156505,
-						HardwayRegisterCount:     266368832,
-						FailedMarshalCount:       75716996,
-						BadReplyCount:            0,
-						MrsRecovered:             7853,
-						MrsOrphaned:              0,
-						MrsAllocated:             0,
-						EmptySendctxQ:            0,
-						TotalRdmaReply:           0,
-						FixupCopyCount:           119328,
-						ReplyWaitsForSend:        1336431717,
-						LocalInvNeeded:           0,
-						NomsgCallCount:           96,
-						BcallCount:               0,
+					Transport: []NFSTransportStats{
+						{
+							Protocol:                 "rdma",
+							Port:                     0,
+							Bind:                     0,
+							Connect:                  5808,
+							ConnectIdleTime:          62,
+							IdleTimeSeconds:          0,
+							Sends:                    494490723,
+							Receives:                 494490687,
+							BadTransactionIDs:        36,
+							CumulativeActiveRequests: 10032963746,
+							CumulativeBacklog:        1282789,
+							MaximumRPCSlotsUsed:      0,
+							CumulativeSendingQueue:   0,
+							CumulativePendingQueue:   0,
+							ReadChunkCount:           107150285,
+							WriteChunkCount:          1226637531,
+							ReplyChunkCount:          2673889,
+							TotalRdmaRequest:         135120843409861,
+							PullupCopyCount:          135119397156505,
+							HardwayRegisterCount:     266368832,
+							FailedMarshalCount:       75716996,
+							BadReplyCount:            0,
+							MrsRecovered:             7853,
+							MrsOrphaned:              0,
+							MrsAllocated:             0,
+							EmptySendctxQ:            0,
+							TotalRdmaReply:           0,
+							FixupCopyCount:           119328,
+							ReplyWaitsForSend:        1336431717,
+							LocalInvNeeded:           0,
+							NomsgCallCount:           96,
+							BcallCount:               0,
+						},
 					},
 				},
 			}},
diff --git a/sysfs/class_fibrechannel_test.go b/sysfs/class_fibrechannel_test.go
index 55f728c..5f1869e 100644
--- a/sysfs/class_fibrechannel_test.go
+++ b/sysfs/class_fibrechannel_test.go
@@ -33,55 +33,109 @@ func TestFibreChannelClass(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	// "host0" FibreChannelHost constants.
+	var (
+		name0             = "host0"
+		speed0            = "16 Gbit"
+		portState0        = "Online"
+		portType0         = "Point-To-Point (direct nport connection)"
+		portName0         = "1000e0071bce95f2"
+		symbolicName0     = "Emulex SN1100E2P FV12.4.270.3 DV12.4.0.0. HN:gotest. OS:Linux"
+		nodeName0         = "2000e0071bce95f2"
+		portID0           = "000002"
+		fabricName0       = "0"
+		devLossTMO0       = "30"
+		supportedClasses0 = "Class 3"
+		supportedSpeeds0  = "4 Gbit, 8 Gbit, 16 Gbit"
+
+		// "host0" FibreChannelHost.Counters constants.
+		dumpedFrames0          = ^uint64(0)
+		errorFrames0           = uint64(0)
+		invalidCRCCount0       = uint64(0x2)
+		rxFrames0              = uint64(0x3)
+		rxWords0               = uint64(0x4)
+		txFrames0              = uint64(0x5)
+		txWords0               = uint64(0x6)
+		secondsSinceLastReset0 = uint64(0x7)
+		invalidTXWordCount0    = uint64(0x8)
+		linkFailureCount0      = uint64(0x9)
+		lossOfSyncCount0       = uint64(0x10)
+		lossOfSignalCount0     = uint64(0x11)
+		nosCount0              = uint64(0x12)
+		fcpPacketAborts0       = uint64(0x13)
+	)
+
+	// "host1" FibreChannelHost constants.
+	var (
+		name1      = "host1"
+		portState1 = "Online"
+
+		// "host1" FibreChannelHost.Counters constants.
+		dumpedFrames1          = uint64(0)
+		errorFrames1           = ^uint64(0)
+		invalidCRCCount1       = uint64(0x20)
+		rxFrames1              = uint64(0x30)
+		rxWords1               = uint64(0x40)
+		txFrames1              = uint64(0x50)
+		txWords1               = uint64(0x60)
+		secondsSinceLastReset1 = uint64(0x70)
+		invalidTXWordCount1    = uint64(0x80)
+		linkFailureCount1      = uint64(0x90)
+		lossOfSyncCount1       = uint64(0x100)
+		lossOfSignalCount1     = uint64(0x110)
+		nosCount1              = uint64(0x120)
+		fcpPacketAborts1       = uint64(0x130)
+	)
+
 	want := FibreChannelClass{
 		"host0": FibreChannelHost{
-			Name:             "host0",
-			Speed:            "16 Gbit",
-			PortState:        "Online",
-			PortType:         "Point-To-Point (direct nport connection)",
-			PortName:         "1000e0071bce95f2",
-			SymbolicName:     "Emulex SN1100E2P FV12.4.270.3 DV12.4.0.0. HN:gotest. OS:Linux",
-			NodeName:         "2000e0071bce95f2",
-			PortID:           "000002",
-			FabricName:       "0",
-			DevLossTMO:       "30",
-			SupportedClasses: "Class 3",
-			SupportedSpeeds:  "4 Gbit, 8 Gbit, 16 Gbit",
-			Counters: FibreChannelCounters{
-				DumpedFrames:          ^uint64(0),
-				ErrorFrames:           0,
-				InvalidCRCCount:       0x2,
-				RXFrames:              0x3,
-				RXWords:               0x4,
-				TXFrames:              0x5,
-				TXWords:               0x6,
-				SecondsSinceLastReset: 0x7,
-				InvalidTXWordCount:    0x8,
-				LinkFailureCount:      0x9,
-				LossOfSyncCount:       0x10,
-				LossOfSignalCount:     0x11,
-				NosCount:              0x12,
-				FCPPacketAborts:       0x13,
+			Name:             &name0,
+			Speed:            &speed0,
+			PortState:        &portState0,
+			PortType:         &portType0,
+			PortName:         &portName0,
+			SymbolicName:     &symbolicName0,
+			NodeName:         &nodeName0,
+			PortID:           &portID0,
+			FabricName:       &fabricName0,
+			DevLossTMO:       &devLossTMO0,
+			SupportedClasses: &supportedClasses0,
+			SupportedSpeeds:  &supportedSpeeds0,
+			Counters: &FibreChannelCounters{
+				DumpedFrames:          &dumpedFrames0,
+				ErrorFrames:           &errorFrames0,
+				InvalidCRCCount:       &invalidCRCCount0,
+				RXFrames:              &rxFrames0,
+				RXWords:               &rxWords0,
+				TXFrames:              &txFrames0,
+				TXWords:               &txWords0,
+				SecondsSinceLastReset: &secondsSinceLastReset0,
+				InvalidTXWordCount:    &invalidTXWordCount0,
+				LinkFailureCount:      &linkFailureCount0,
+				LossOfSyncCount:       &lossOfSyncCount0,
+				LossOfSignalCount:     &lossOfSignalCount0,
+				NosCount:              &nosCount0,
+				FCPPacketAborts:       &fcpPacketAborts0,
 			},
 		},
 		"host1": FibreChannelHost{
-			Name:      "host1",
-			PortState: "Online",
-			Counters: FibreChannelCounters{
-				DumpedFrames:          0,
-				ErrorFrames:           ^uint64(0),
-				InvalidCRCCount:       0x20,
-				RXFrames:              0x30,
-				RXWords:               0x40,
-				TXFrames:              0x50,
-				TXWords:               0x60,
-				SecondsSinceLastReset: 0x70,
-				InvalidTXWordCount:    0x80,
-				LinkFailureCount:      0x90,
-				LossOfSyncCount:       0x100,
-				LossOfSignalCount:     0x110,
-				NosCount:              0x120,
-				FCPPacketAborts:       0x130,
+			Name:      &name1,
+			PortState: &portState1,
+			Counters: &FibreChannelCounters{
+				DumpedFrames:          &dumpedFrames1,
+				ErrorFrames:           &errorFrames1,
+				InvalidCRCCount:       &invalidCRCCount1,
+				RXFrames:              &rxFrames1,
+				RXWords:               &rxWords1,
+				TXFrames:              &txFrames1,
+				TXWords:               &txWords1,
+				SecondsSinceLastReset: &secondsSinceLastReset1,
+				InvalidTXWordCount:    &invalidTXWordCount1,
+				LinkFailureCount:      &linkFailureCount1,
+				LossOfSyncCount:       &lossOfSyncCount1,
+				LossOfSignalCount:     &lossOfSignalCount1,
+				NosCount:              &nosCount1,
+				FCPPacketAborts:       &fcpPacketAborts1,
 			},
 		},
 	}
diff --git a/sysfs/system_cpu_test.go b/sysfs/system_cpu_test.go
index 9fee2a1..577c118 100644
--- a/sysfs/system_cpu_test.go
+++ b/sysfs/system_cpu_test.go
@@ -104,23 +104,38 @@ func TestSystemCpufreq(t *testing.T) {
 	}
 
 	systemCpufreq := []SystemCPUCpufreqStats{
-		// Has missing `cpuinfo_cur_freq` file.
+		// The following files are missing for the first CPU:
+		// * `cpuinfo_cur_freq`
+		// * `time_in_state`
+		// * `total_trans`
 		{
-			Name:                     "0",
-			CpuinfoCurrentFrequency:  nil,
-			CpuinfoMinimumFrequency:  makeUint64(800000),
-			CpuinfoMaximumFrequency:  makeUint64(2400000),
-			CpuinfoTransitionLatency: makeUint64(0),
-			ScalingCurrentFrequency:  makeUint64(1219917),
-			ScalingMinimumFrequency:  makeUint64(800000),
-			ScalingMaximumFrequency:  makeUint64(2400000),
-			AvailableGovernors:       "performance powersave",
-			Driver:                   "intel_pstate",
-			Governor:                 "powersave",
-			RelatedCpus:              "0",
-			SetSpeed:                 "<unsupported>",
+			Name:                             "0",
+			CpuinfoCurrentFrequency:          nil,
+			CpuinfoMinimumFrequency:          makeUint64(800000),
+			CpuinfoMaximumFrequency:          makeUint64(2400000),
+			CpuinfoTransitionLatency:         makeUint64(0),
+			ScalingCurrentFrequency:          makeUint64(1219917),
+			ScalingMinimumFrequency:          makeUint64(800000),
+			ScalingMaximumFrequency:          makeUint64(2400000),
+			AvailableGovernors:               "performance powersave",
+			Driver:                           "intel_pstate",
+			Governor:                         "powersave",
+			RelatedCpus:                      "0",
+			SetSpeed:                         "<unsupported>",
+			CpuinfoFrequencyDuration:         nil,
+			CpuinfoFrequencyTransitionsTotal: nil,
+			CpuinfoTransitionTable: &[][]uint64{
+				{0, 3600000, 3400000, 3200000, 3000000, 2800000},
+				{3600000, 0, 5, 0, 0, 0},
+				{3400000, 4, 0, 2, 0, 0},
+				{3200000, 0, 1, 0, 2, 0},
+				{3000000, 0, 0, 1, 0, 3},
+				{2800000, 0, 0, 0, 2, 0},
+			},
 		},
-		// Has missing `scaling_cur_freq` file.
+		// The following files are missing for the second CPU:
+		// * `scaling_cur_freq`
+		// * `trans_table`
 		{
 			Name:                     "1",
 			CpuinfoCurrentFrequency:  makeUint64(1200195),
@@ -135,6 +150,15 @@ func TestSystemCpufreq(t *testing.T) {
 			Governor:                 "powersave",
 			RelatedCpus:              "1",
 			SetSpeed:                 "<unsupported>",
+			CpuinfoFrequencyDuration: &map[uint64]uint64{
+				3600000: 2089,
+				3400000: 136,
+				3200000: 34,
+				3000000: 67,
+				2800000: 172488,
+			},
+			CpuinfoFrequencyTransitionsTotal: makeUint64(20),
+			CpuinfoTransitionTable:           nil,
 		},
 	}
 
diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar
index 170a526..a998b27 100644
--- a/testdata/fixtures.ttar
+++ b/testdata/fixtures.ttar
@@ -13548,6 +13548,23 @@ Lines: 1
 <unsupported>
 Mode: 664
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state
+Lines: 5
+3600000 2089
+3400000 136
+3200000 34
+3000000 67
+2800000 172488
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpu1/cpufreq/stats/total_trans
+Lines: 1
+20
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/cpu/cpu1/thermal_throttle
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -13663,6 +13680,20 @@ Lines: 1
 <unsupported>
 Mode: 644
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/devices/system/cpu/cpufreq/policy0/stats
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/cpufreq/policy0/stats/trans_table
+Lines: 7
+From  :    To
+        :   3600000   3400000   3200000   3000000   2800000
+3600000:         0         5         0         0         0
+3400000:         4         0         2         0         0
+3200000:         0         1         0         2         0
+3000000:         0         0         1         0         3
+2800000:         0         0         0         2         0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/devices/system/cpu/cpufreq/policy1
 Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -