123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- package procfs
- // While implementing parsing of /proc/[pid]/mountstats, this blog was used
- // heavily as a reference:
- // https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex
- //
- // Special thanks to Chris Siebenmann for all of his posts explaining the
- // various statistics available for NFS.
- import (
- "bufio"
- "fmt"
- "io"
- "strconv"
- "strings"
- "time"
- )
- // Constants shared between multiple functions.
- const (
- deviceEntryLen = 8
- fieldBytesLen = 8
- fieldEventsLen = 27
- statVersion10 = "1.0"
- statVersion11 = "1.1"
- fieldTransport10Len = 10
- fieldTransport11Len = 13
- )
- // A Mount is a device mount parsed from /proc/[pid]/mountstats.
- type Mount struct {
- // Name of the device.
- Device string
- // The mount point of the device.
- Mount string
- // The filesystem type used by the device.
- Type string
- // If available additional statistics related to this Mount.
- // Use a type assertion to determine if additional statistics are available.
- Stats MountStats
- }
- // A MountStats is a type which contains detailed statistics for a specific
- // type of Mount.
- type MountStats interface {
- mountStats()
- }
- // A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
- type MountStatsNFS struct {
- // The version of statistics provided.
- StatVersion string
- // The age of the NFS mount.
- Age time.Duration
- // Statistics related to byte counters for various operations.
- Bytes NFSBytesStats
- // Statistics related to various NFS event occurrences.
- Events NFSEventsStats
- // Statistics broken down by filesystem operation.
- Operations []NFSOperationStats
- // Statistics about the NFS RPC transport.
- Transport NFSTransportStats
- }
- // mountStats implements MountStats.
- func (m MountStatsNFS) mountStats() {}
- // A NFSBytesStats contains statistics about the number of bytes read and written
- // by an NFS client to and from an NFS server.
- type NFSBytesStats struct {
- // Number of bytes read using the read() syscall.
- Read int
- // Number of bytes written using the write() syscall.
- Write int
- // Number of bytes read using the read() syscall in O_DIRECT mode.
- DirectRead int
- // Number of bytes written using the write() syscall in O_DIRECT mode.
- DirectWrite int
- // Number of bytes read from the NFS server, in total.
- ReadTotal int
- // Number of bytes written to the NFS server, in total.
- WriteTotal int
- // Number of pages read directly via mmap()'d files.
- ReadPages int
- // Number of pages written directly via mmap()'d files.
- WritePages int
- }
- // A NFSEventsStats contains statistics about NFS event occurrences.
- type NFSEventsStats struct {
- // Number of times cached inode attributes are re-validated from the server.
- InodeRevalidate int
- // Number of times cached dentry nodes are re-validated from the server.
- DnodeRevalidate int
- // Number of times an inode cache is cleared.
- DataInvalidate int
- // Number of times cached inode attributes are invalidated.
- AttributeInvalidate int
- // Number of times files or directories have been open()'d.
- VFSOpen int
- // Number of times a directory lookup has occurred.
- VFSLookup int
- // Number of times permissions have been checked.
- VFSAccess int
- // Number of updates (and potential writes) to pages.
- VFSUpdatePage int
- // Number of pages read directly via mmap()'d files.
- VFSReadPage int
- // Number of times a group of pages have been read.
- VFSReadPages int
- // Number of pages written directly via mmap()'d files.
- VFSWritePage int
- // Number of times a group of pages have been written.
- VFSWritePages int
- // Number of times directory entries have been read with getdents().
- VFSGetdents int
- // Number of times attributes have been set on inodes.
- VFSSetattr int
- // Number of pending writes that have been forcefully flushed to the server.
- VFSFlush int
- // Number of times fsync() has been called on directories and files.
- VFSFsync int
- // Number of times locking has been attemped on a file.
- VFSLock int
- // Number of times files have been closed and released.
- VFSFileRelease int
- // Unknown. Possibly unused.
- CongestionWait int
- // Number of times files have been truncated.
- Truncation int
- // Number of times a file has been grown due to writes beyond its existing end.
- WriteExtension int
- // Number of times a file was removed while still open by another process.
- SillyRename int
- // Number of times the NFS server gave less data than expected while reading.
- ShortRead int
- // Number of times the NFS server wrote less data than expected while writing.
- ShortWrite int
- // Number of times the NFS server indicated EJUKEBOX; retrieving data from
- // offline storage.
- JukeboxDelay int
- // Number of NFS v4.1+ pNFS reads.
- PNFSRead int
- // Number of NFS v4.1+ pNFS writes.
- PNFSWrite int
- }
- // A NFSOperationStats contains statistics for a single operation.
- type NFSOperationStats struct {
- // The name of the operation.
- Operation string
- // Number of requests performed for this operation.
- Requests int
- // Number of times an actual RPC request has been transmitted for this operation.
- Transmissions int
- // Number of times a request has had a major timeout.
- MajorTimeouts int
- // Number of bytes sent for this operation, including RPC headers and payload.
- BytesSent int
- // Number of bytes received for this operation, including RPC headers and payload.
- BytesReceived int
- // Duration all requests spent queued for transmission before they were sent.
- CumulativeQueueTime time.Duration
- // Duration it took to get a reply back after the request was transmitted.
- CumulativeTotalResponseTime time.Duration
- // Duration from when a request was enqueued to when it was completely handled.
- CumulativeTotalRequestTime time.Duration
- }
- // A NFSTransportStats contains statistics for the NFS mount RPC requests and
- // responses.
- type NFSTransportStats struct {
- // The local port used for the NFS mount.
- Port int
- // Number of times the client has had to establish a connection from scratch
- // to the NFS server.
- Bind int
- // Number of times the client has made a TCP connection to the NFS server.
- Connect int
- // Duration (in jiffies, a kernel internal unit of time) the NFS mount has
- // spent waiting for connections to the server to be established.
- ConnectIdleTime int
- // Duration since the NFS mount last saw any RPC traffic.
- IdleTime time.Duration
- // Number of RPC requests for this mount sent to the NFS server.
- Sends int
- // Number of RPC responses for this mount received from the NFS server.
- Receives int
- // Number of times the NFS server sent a response with a transaction ID
- // unknown to this client.
- BadTransactionIDs int
- // A running counter, incremented on each request as the current difference
- // ebetween sends and receives.
- CumulativeActiveRequests int
- // A running counter, incremented on each request by the current backlog
- // queue size.
- CumulativeBacklog int
- // Stats below only available with stat version 1.1.
- // Maximum number of simultaneously active RPC requests ever used.
- MaximumRPCSlotsUsed int
- // A running counter, incremented on each request as the current size of the
- // sending queue.
- CumulativeSendingQueue int
- // A running counter, incremented on each request as the current size of the
- // pending queue.
- CumulativePendingQueue int
- }
- // parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
- // of Mount structures containing detailed information about each mount.
- // If available, statistics for each mount are parsed as well.
- func parseMountStats(r io.Reader) ([]*Mount, error) {
- const (
- device = "device"
- statVersionPrefix = "statvers="
- nfs3Type = "nfs"
- nfs4Type = "nfs4"
- )
- var mounts []*Mount
- s := bufio.NewScanner(r)
- for s.Scan() {
- // Only look for device entries in this function
- ss := strings.Fields(string(s.Bytes()))
- if len(ss) == 0 || ss[0] != device {
- continue
- }
- m, err := parseMount(ss)
- if err != nil {
- return nil, err
- }
- // Does this mount also possess statistics information?
- if len(ss) > deviceEntryLen {
- // Only NFSv3 and v4 are supported for parsing statistics
- if m.Type != nfs3Type && m.Type != nfs4Type {
- return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)
- }
- statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
- stats, err := parseMountStatsNFS(s, statVersion)
- if err != nil {
- return nil, err
- }
- m.Stats = stats
- }
- mounts = append(mounts, m)
- }
- return mounts, s.Err()
- }
- // parseMount parses an entry in /proc/[pid]/mountstats in the format:
- // device [device] mounted on [mount] with fstype [type]
- func parseMount(ss []string) (*Mount, error) {
- if len(ss) < deviceEntryLen {
- return nil, fmt.Errorf("invalid device entry: %v", ss)
- }
- // Check for specific words appearing at specific indices to ensure
- // the format is consistent with what we expect
- format := []struct {
- i int
- s string
- }{
- {i: 0, s: "device"},
- {i: 2, s: "mounted"},
- {i: 3, s: "on"},
- {i: 5, s: "with"},
- {i: 6, s: "fstype"},
- }
- for _, f := range format {
- if ss[f.i] != f.s {
- return nil, fmt.Errorf("invalid device entry: %v", ss)
- }
- }
- return &Mount{
- Device: ss[1],
- Mount: ss[4],
- Type: ss[7],
- }, nil
- }
- // parseMountStatsNFS parses a MountStatsNFS by scanning additional information
- // related to NFS statistics.
- func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
- // Field indicators for parsing specific types of data
- const (
- fieldAge = "age:"
- fieldBytes = "bytes:"
- fieldEvents = "events:"
- fieldPerOpStats = "per-op"
- fieldTransport = "xprt:"
- )
- stats := &MountStatsNFS{
- StatVersion: statVersion,
- }
- for s.Scan() {
- ss := strings.Fields(string(s.Bytes()))
- if len(ss) == 0 {
- break
- }
- if len(ss) < 2 {
- return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
- }
- switch ss[0] {
- case fieldAge:
- // Age integer is in seconds
- d, err := time.ParseDuration(ss[1] + "s")
- if err != nil {
- return nil, err
- }
- stats.Age = d
- case fieldBytes:
- bstats, err := parseNFSBytesStats(ss[1:])
- if err != nil {
- return nil, err
- }
- stats.Bytes = *bstats
- case fieldEvents:
- estats, err := parseNFSEventsStats(ss[1:])
- if err != nil {
- return nil, err
- }
- stats.Events = *estats
- case fieldTransport:
- if len(ss) < 3 {
- return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
- }
- tstats, err := parseNFSTransportStats(ss[2:], statVersion)
- if err != nil {
- return nil, err
- }
- stats.Transport = *tstats
- }
- // When encountering "per-operation statistics", we must break this
- // loop and parse them seperately to ensure we can terminate parsing
- // before reaching another device entry; hence why this 'if' statement
- // is not just another switch case
- if ss[0] == fieldPerOpStats {
- break
- }
- }
- if err := s.Err(); err != nil {
- return nil, err
- }
- // NFS per-operation stats appear last before the next device entry
- perOpStats, err := parseNFSOperationStats(s)
- if err != nil {
- return nil, err
- }
- stats.Operations = perOpStats
- return stats, nil
- }
- // parseNFSBytesStats parses a NFSBytesStats line using an input set of
- // integer fields.
- func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
- if len(ss) != fieldBytesLen {
- return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)
- }
- ns := make([]int, 0, fieldBytesLen)
- for _, s := range ss {
- n, err := strconv.Atoi(s)
- if err != nil {
- return nil, err
- }
- ns = append(ns, n)
- }
- return &NFSBytesStats{
- Read: ns[0],
- Write: ns[1],
- DirectRead: ns[2],
- DirectWrite: ns[3],
- ReadTotal: ns[4],
- WriteTotal: ns[5],
- ReadPages: ns[6],
- WritePages: ns[7],
- }, nil
- }
- // parseNFSEventsStats parses a NFSEventsStats line using an input set of
- // integer fields.
- func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
- if len(ss) != fieldEventsLen {
- return nil, fmt.Errorf("invalid NFS events stats: %v", ss)
- }
- ns := make([]int, 0, fieldEventsLen)
- for _, s := range ss {
- n, err := strconv.Atoi(s)
- if err != nil {
- return nil, err
- }
- ns = append(ns, n)
- }
- return &NFSEventsStats{
- InodeRevalidate: ns[0],
- DnodeRevalidate: ns[1],
- DataInvalidate: ns[2],
- AttributeInvalidate: ns[3],
- VFSOpen: ns[4],
- VFSLookup: ns[5],
- VFSAccess: ns[6],
- VFSUpdatePage: ns[7],
- VFSReadPage: ns[8],
- VFSReadPages: ns[9],
- VFSWritePage: ns[10],
- VFSWritePages: ns[11],
- VFSGetdents: ns[12],
- VFSSetattr: ns[13],
- VFSFlush: ns[14],
- VFSFsync: ns[15],
- VFSLock: ns[16],
- VFSFileRelease: ns[17],
- CongestionWait: ns[18],
- Truncation: ns[19],
- WriteExtension: ns[20],
- SillyRename: ns[21],
- ShortRead: ns[22],
- ShortWrite: ns[23],
- JukeboxDelay: ns[24],
- PNFSRead: ns[25],
- PNFSWrite: ns[26],
- }, nil
- }
- // parseNFSOperationStats parses a slice of NFSOperationStats by scanning
- // additional information about per-operation statistics until an empty
- // line is reached.
- func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
- const (
- // Number of expected fields in each per-operation statistics set
- numFields = 9
- )
- var ops []NFSOperationStats
- for s.Scan() {
- ss := strings.Fields(string(s.Bytes()))
- if len(ss) == 0 {
- // Must break when reading a blank line after per-operation stats to
- // enable top-level function to parse the next device entry
- break
- }
- if len(ss) != numFields {
- return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
- }
- // Skip string operation name for integers
- ns := make([]int, 0, numFields-1)
- for _, st := range ss[1:] {
- n, err := strconv.Atoi(st)
- if err != nil {
- return nil, err
- }
- ns = append(ns, n)
- }
- ops = append(ops, NFSOperationStats{
- Operation: strings.TrimSuffix(ss[0], ":"),
- Requests: ns[0],
- Transmissions: ns[1],
- MajorTimeouts: ns[2],
- BytesSent: ns[3],
- BytesReceived: ns[4],
- CumulativeQueueTime: time.Duration(ns[5]) * time.Millisecond,
- CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond,
- CumulativeTotalRequestTime: time.Duration(ns[7]) * time.Millisecond,
- })
- }
- return ops, s.Err()
- }
- // parseNFSTransportStats parses a NFSTransportStats line using an input set of
- // integer fields matched to a specific stats version.
- func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
- switch statVersion {
- case statVersion10:
- if len(ss) != fieldTransport10Len {
- return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
- }
- case statVersion11:
- if len(ss) != fieldTransport11Len {
- return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
- }
- default:
- return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion)
- }
- // Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
- // in a v1.0 response
- ns := make([]int, 0, fieldTransport11Len)
- for _, s := range ss {
- n, err := strconv.Atoi(s)
- if err != nil {
- return nil, err
- }
- ns = append(ns, n)
- }
- return &NFSTransportStats{
- Port: ns[0],
- Bind: ns[1],
- Connect: ns[2],
- ConnectIdleTime: ns[3],
- IdleTime: time.Duration(ns[4]) * time.Second,
- Sends: ns[5],
- Receives: ns[6],
- BadTransactionIDs: ns[7],
- CumulativeActiveRequests: ns[8],
- CumulativeBacklog: ns[9],
- MaximumRPCSlotsUsed: ns[10],
- CumulativeSendingQueue: ns[11],
- CumulativePendingQueue: ns[12],
- }, nil
- }
|