...

Source file src/github.com/prometheus/procfs/mountstats.go

Documentation: github.com/prometheus/procfs

     1  // Copyright 2018 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package procfs
    15  
    16  // While implementing parsing of /proc/[pid]/mountstats, this blog was used
    17  // heavily as a reference:
    18  //   https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex
    19  //
    20  // Special thanks to Chris Siebenmann for all of his posts explaining the
    21  // various statistics available for NFS.
    22  
    23  import (
    24  	"bufio"
    25  	"fmt"
    26  	"io"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  )
    31  
    32  // Constants shared between multiple functions.
    33  const (
    34  	deviceEntryLen = 8
    35  
    36  	fieldBytesLen  = 8
    37  	fieldEventsLen = 27
    38  
    39  	statVersion10 = "1.0"
    40  	statVersion11 = "1.1"
    41  
    42  	fieldTransport10TCPLen = 10
    43  	fieldTransport10UDPLen = 7
    44  
    45  	fieldTransport11TCPLen = 13
    46  	fieldTransport11UDPLen = 10
    47  
    48  	// kernel version >= 4.14 MaxLen
    49  	// See: https://elixir.bootlin.com/linux/v6.4.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L393
    50  	fieldTransport11RDMAMaxLen = 28
    51  
    52  	// kernel version <= 4.2 MinLen
    53  	// See: https://elixir.bootlin.com/linux/v4.2.8/source/net/sunrpc/xprtrdma/xprt_rdma.h#L331
    54  	fieldTransport11RDMAMinLen = 20
    55  )
    56  
    57  // A Mount is a device mount parsed from /proc/[pid]/mountstats.
    58  type Mount struct {
    59  	// Name of the device.
    60  	Device string
    61  	// The mount point of the device.
    62  	Mount string
    63  	// The filesystem type used by the device.
    64  	Type string
    65  	// If available additional statistics related to this Mount.
    66  	// Use a type assertion to determine if additional statistics are available.
    67  	Stats MountStats
    68  }
    69  
    70  // A MountStats is a type which contains detailed statistics for a specific
    71  // type of Mount.
    72  type MountStats interface {
    73  	mountStats()
    74  }
    75  
    76  // A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
    77  type MountStatsNFS struct {
    78  	// The version of statistics provided.
    79  	StatVersion string
    80  	// The mount options of the NFS mount.
    81  	Opts map[string]string
    82  	// The age of the NFS mount.
    83  	Age time.Duration
    84  	// Statistics related to byte counters for various operations.
    85  	Bytes NFSBytesStats
    86  	// Statistics related to various NFS event occurrences.
    87  	Events NFSEventsStats
    88  	// Statistics broken down by filesystem operation.
    89  	Operations []NFSOperationStats
    90  	// Statistics about the NFS RPC transport.
    91  	Transport NFSTransportStats
    92  }
    93  
    94  // mountStats implements MountStats.
    95  func (m MountStatsNFS) mountStats() {}
    96  
    97  // A NFSBytesStats contains statistics about the number of bytes read and written
    98  // by an NFS client to and from an NFS server.
    99  type NFSBytesStats struct {
   100  	// Number of bytes read using the read() syscall.
   101  	Read uint64
   102  	// Number of bytes written using the write() syscall.
   103  	Write uint64
   104  	// Number of bytes read using the read() syscall in O_DIRECT mode.
   105  	DirectRead uint64
   106  	// Number of bytes written using the write() syscall in O_DIRECT mode.
   107  	DirectWrite uint64
   108  	// Number of bytes read from the NFS server, in total.
   109  	ReadTotal uint64
   110  	// Number of bytes written to the NFS server, in total.
   111  	WriteTotal uint64
   112  	// Number of pages read directly via mmap()'d files.
   113  	ReadPages uint64
   114  	// Number of pages written directly via mmap()'d files.
   115  	WritePages uint64
   116  }
   117  
   118  // A NFSEventsStats contains statistics about NFS event occurrences.
   119  type NFSEventsStats struct {
   120  	// Number of times cached inode attributes are re-validated from the server.
   121  	InodeRevalidate uint64
   122  	// Number of times cached dentry nodes are re-validated from the server.
   123  	DnodeRevalidate uint64
   124  	// Number of times an inode cache is cleared.
   125  	DataInvalidate uint64
   126  	// Number of times cached inode attributes are invalidated.
   127  	AttributeInvalidate uint64
   128  	// Number of times files or directories have been open()'d.
   129  	VFSOpen uint64
   130  	// Number of times a directory lookup has occurred.
   131  	VFSLookup uint64
   132  	// Number of times permissions have been checked.
   133  	VFSAccess uint64
   134  	// Number of updates (and potential writes) to pages.
   135  	VFSUpdatePage uint64
   136  	// Number of pages read directly via mmap()'d files.
   137  	VFSReadPage uint64
   138  	// Number of times a group of pages have been read.
   139  	VFSReadPages uint64
   140  	// Number of pages written directly via mmap()'d files.
   141  	VFSWritePage uint64
   142  	// Number of times a group of pages have been written.
   143  	VFSWritePages uint64
   144  	// Number of times directory entries have been read with getdents().
   145  	VFSGetdents uint64
   146  	// Number of times attributes have been set on inodes.
   147  	VFSSetattr uint64
   148  	// Number of pending writes that have been forcefully flushed to the server.
   149  	VFSFlush uint64
   150  	// Number of times fsync() has been called on directories and files.
   151  	VFSFsync uint64
   152  	// Number of times locking has been attempted on a file.
   153  	VFSLock uint64
   154  	// Number of times files have been closed and released.
   155  	VFSFileRelease uint64
   156  	// Unknown.  Possibly unused.
   157  	CongestionWait uint64
   158  	// Number of times files have been truncated.
   159  	Truncation uint64
   160  	// Number of times a file has been grown due to writes beyond its existing end.
   161  	WriteExtension uint64
   162  	// Number of times a file was removed while still open by another process.
   163  	SillyRename uint64
   164  	// Number of times the NFS server gave less data than expected while reading.
   165  	ShortRead uint64
   166  	// Number of times the NFS server wrote less data than expected while writing.
   167  	ShortWrite uint64
   168  	// Number of times the NFS server indicated EJUKEBOX; retrieving data from
   169  	// offline storage.
   170  	JukeboxDelay uint64
   171  	// Number of NFS v4.1+ pNFS reads.
   172  	PNFSRead uint64
   173  	// Number of NFS v4.1+ pNFS writes.
   174  	PNFSWrite uint64
   175  }
   176  
   177  // A NFSOperationStats contains statistics for a single operation.
   178  type NFSOperationStats struct {
   179  	// The name of the operation.
   180  	Operation string
   181  	// Number of requests performed for this operation.
   182  	Requests uint64
   183  	// Number of times an actual RPC request has been transmitted for this operation.
   184  	Transmissions uint64
   185  	// Number of times a request has had a major timeout.
   186  	MajorTimeouts uint64
   187  	// Number of bytes sent for this operation, including RPC headers and payload.
   188  	BytesSent uint64
   189  	// Number of bytes received for this operation, including RPC headers and payload.
   190  	BytesReceived uint64
   191  	// Duration all requests spent queued for transmission before they were sent.
   192  	CumulativeQueueMilliseconds uint64
   193  	// Duration it took to get a reply back after the request was transmitted.
   194  	CumulativeTotalResponseMilliseconds uint64
   195  	// Duration from when a request was enqueued to when it was completely handled.
   196  	CumulativeTotalRequestMilliseconds uint64
   197  	// The average time from the point the client sends RPC requests until it receives the response.
   198  	AverageRTTMilliseconds float64
   199  	// The count of operations that complete with tk_status < 0.  These statuses usually indicate error conditions.
   200  	Errors uint64
   201  }
   202  
   203  // A NFSTransportStats contains statistics for the NFS mount RPC requests and
   204  // responses.
   205  type NFSTransportStats struct {
   206  	// The transport protocol used for the NFS mount.
   207  	Protocol string
   208  	// The local port used for the NFS mount.
   209  	Port uint64
   210  	// Number of times the client has had to establish a connection from scratch
   211  	// to the NFS server.
   212  	Bind uint64
   213  	// Number of times the client has made a TCP connection to the NFS server.
   214  	Connect uint64
   215  	// Duration (in jiffies, a kernel internal unit of time) the NFS mount has
   216  	// spent waiting for connections to the server to be established.
   217  	ConnectIdleTime uint64
   218  	// Duration since the NFS mount last saw any RPC traffic.
   219  	IdleTimeSeconds uint64
   220  	// Number of RPC requests for this mount sent to the NFS server.
   221  	Sends uint64
   222  	// Number of RPC responses for this mount received from the NFS server.
   223  	Receives uint64
   224  	// Number of times the NFS server sent a response with a transaction ID
   225  	// unknown to this client.
   226  	BadTransactionIDs uint64
   227  	// A running counter, incremented on each request as the current difference
   228  	// ebetween sends and receives.
   229  	CumulativeActiveRequests uint64
   230  	// A running counter, incremented on each request by the current backlog
   231  	// queue size.
   232  	CumulativeBacklog uint64
   233  
   234  	// Stats below only available with stat version 1.1.
   235  
   236  	// Maximum number of simultaneously active RPC requests ever used.
   237  	MaximumRPCSlotsUsed uint64
   238  	// A running counter, incremented on each request as the current size of the
   239  	// sending queue.
   240  	CumulativeSendingQueue uint64
   241  	// A running counter, incremented on each request as the current size of the
   242  	// pending queue.
   243  	CumulativePendingQueue uint64
   244  
   245  	// Stats below only available with stat version 1.1.
   246  	// Transport over RDMA
   247  
   248  	// accessed when sending a call
   249  	ReadChunkCount   uint64
   250  	WriteChunkCount  uint64
   251  	ReplyChunkCount  uint64
   252  	TotalRdmaRequest uint64
   253  
   254  	// rarely accessed error counters
   255  	PullupCopyCount      uint64
   256  	HardwayRegisterCount uint64
   257  	FailedMarshalCount   uint64
   258  	BadReplyCount        uint64
   259  	MrsRecovered         uint64
   260  	MrsOrphaned          uint64
   261  	MrsAllocated         uint64
   262  	EmptySendctxQ        uint64
   263  
   264  	// accessed when receiving a reply
   265  	TotalRdmaReply    uint64
   266  	FixupCopyCount    uint64
   267  	ReplyWaitsForSend uint64
   268  	LocalInvNeeded    uint64
   269  	NomsgCallCount    uint64
   270  	BcallCount        uint64
   271  }
   272  
   273  // parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
   274  // of Mount structures containing detailed information about each mount.
   275  // If available, statistics for each mount are parsed as well.
   276  func parseMountStats(r io.Reader) ([]*Mount, error) {
   277  	const (
   278  		device            = "device"
   279  		statVersionPrefix = "statvers="
   280  
   281  		nfs3Type = "nfs"
   282  		nfs4Type = "nfs4"
   283  	)
   284  
   285  	var mounts []*Mount
   286  
   287  	s := bufio.NewScanner(r)
   288  	for s.Scan() {
   289  		// Only look for device entries in this function
   290  		ss := strings.Fields(string(s.Bytes()))
   291  		if len(ss) == 0 || ss[0] != device {
   292  			continue
   293  		}
   294  
   295  		m, err := parseMount(ss)
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  
   300  		// Does this mount also possess statistics information?
   301  		if len(ss) > deviceEntryLen {
   302  			// Only NFSv3 and v4 are supported for parsing statistics
   303  			if m.Type != nfs3Type && m.Type != nfs4Type {
   304  				return nil, fmt.Errorf("%w: Cannot parse MountStats for %q", ErrFileParse, m.Type)
   305  			}
   306  
   307  			statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
   308  
   309  			stats, err := parseMountStatsNFS(s, statVersion)
   310  			if err != nil {
   311  				return nil, err
   312  			}
   313  
   314  			m.Stats = stats
   315  		}
   316  
   317  		mounts = append(mounts, m)
   318  	}
   319  
   320  	return mounts, s.Err()
   321  }
   322  
   323  // parseMount parses an entry in /proc/[pid]/mountstats in the format:
   324  //
   325  //	device [device] mounted on [mount] with fstype [type]
   326  func parseMount(ss []string) (*Mount, error) {
   327  	if len(ss) < deviceEntryLen {
   328  		return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
   329  	}
   330  
   331  	// Check for specific words appearing at specific indices to ensure
   332  	// the format is consistent with what we expect
   333  	format := []struct {
   334  		i int
   335  		s string
   336  	}{
   337  		{i: 0, s: "device"},
   338  		{i: 2, s: "mounted"},
   339  		{i: 3, s: "on"},
   340  		{i: 5, s: "with"},
   341  		{i: 6, s: "fstype"},
   342  	}
   343  
   344  	for _, f := range format {
   345  		if ss[f.i] != f.s {
   346  			return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
   347  		}
   348  	}
   349  
   350  	return &Mount{
   351  		Device: ss[1],
   352  		Mount:  ss[4],
   353  		Type:   ss[7],
   354  	}, nil
   355  }
   356  
   357  // parseMountStatsNFS parses a MountStatsNFS by scanning additional information
   358  // related to NFS statistics.
   359  func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
   360  	// Field indicators for parsing specific types of data
   361  	const (
   362  		fieldOpts       = "opts:"
   363  		fieldAge        = "age:"
   364  		fieldBytes      = "bytes:"
   365  		fieldEvents     = "events:"
   366  		fieldPerOpStats = "per-op"
   367  		fieldTransport  = "xprt:"
   368  	)
   369  
   370  	stats := &MountStatsNFS{
   371  		StatVersion: statVersion,
   372  	}
   373  
   374  	for s.Scan() {
   375  		ss := strings.Fields(string(s.Bytes()))
   376  		if len(ss) == 0 {
   377  			break
   378  		}
   379  
   380  		switch ss[0] {
   381  		case fieldOpts:
   382  			if len(ss) < 2 {
   383  				return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
   384  			}
   385  			if stats.Opts == nil {
   386  				stats.Opts = map[string]string{}
   387  			}
   388  			for _, opt := range strings.Split(ss[1], ",") {
   389  				split := strings.Split(opt, "=")
   390  				if len(split) == 2 {
   391  					stats.Opts[split[0]] = split[1]
   392  				} else {
   393  					stats.Opts[opt] = ""
   394  				}
   395  			}
   396  		case fieldAge:
   397  			if len(ss) < 2 {
   398  				return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
   399  			}
   400  			// Age integer is in seconds
   401  			d, err := time.ParseDuration(ss[1] + "s")
   402  			if err != nil {
   403  				return nil, err
   404  			}
   405  
   406  			stats.Age = d
   407  		case fieldBytes:
   408  			if len(ss) < 2 {
   409  				return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
   410  			}
   411  			bstats, err := parseNFSBytesStats(ss[1:])
   412  			if err != nil {
   413  				return nil, err
   414  			}
   415  
   416  			stats.Bytes = *bstats
   417  		case fieldEvents:
   418  			if len(ss) < 2 {
   419  				return nil, fmt.Errorf("%w: Incomplete information for NFS events: %v", ErrFileParse, ss)
   420  			}
   421  			estats, err := parseNFSEventsStats(ss[1:])
   422  			if err != nil {
   423  				return nil, err
   424  			}
   425  
   426  			stats.Events = *estats
   427  		case fieldTransport:
   428  			if len(ss) < 3 {
   429  				return nil, fmt.Errorf("%w: Incomplete information for NFS transport stats: %v", ErrFileParse, ss)
   430  			}
   431  
   432  			tstats, err := parseNFSTransportStats(ss[1:], statVersion)
   433  			if err != nil {
   434  				return nil, err
   435  			}
   436  
   437  			stats.Transport = *tstats
   438  		}
   439  
   440  		// When encountering "per-operation statistics", we must break this
   441  		// loop and parse them separately to ensure we can terminate parsing
   442  		// before reaching another device entry; hence why this 'if' statement
   443  		// is not just another switch case
   444  		if ss[0] == fieldPerOpStats {
   445  			break
   446  		}
   447  	}
   448  
   449  	if err := s.Err(); err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	// NFS per-operation stats appear last before the next device entry
   454  	perOpStats, err := parseNFSOperationStats(s)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	stats.Operations = perOpStats
   460  
   461  	return stats, nil
   462  }
   463  
   464  // parseNFSBytesStats parses a NFSBytesStats line using an input set of
   465  // integer fields.
   466  func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
   467  	if len(ss) != fieldBytesLen {
   468  		return nil, fmt.Errorf("%w: Invalid NFS bytes stats: %v", ErrFileParse, ss)
   469  	}
   470  
   471  	ns := make([]uint64, 0, fieldBytesLen)
   472  	for _, s := range ss {
   473  		n, err := strconv.ParseUint(s, 10, 64)
   474  		if err != nil {
   475  			return nil, err
   476  		}
   477  
   478  		ns = append(ns, n)
   479  	}
   480  
   481  	return &NFSBytesStats{
   482  		Read:        ns[0],
   483  		Write:       ns[1],
   484  		DirectRead:  ns[2],
   485  		DirectWrite: ns[3],
   486  		ReadTotal:   ns[4],
   487  		WriteTotal:  ns[5],
   488  		ReadPages:   ns[6],
   489  		WritePages:  ns[7],
   490  	}, nil
   491  }
   492  
   493  // parseNFSEventsStats parses a NFSEventsStats line using an input set of
   494  // integer fields.
   495  func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
   496  	if len(ss) != fieldEventsLen {
   497  		return nil, fmt.Errorf("%w: invalid NFS events stats: %v", ErrFileParse, ss)
   498  	}
   499  
   500  	ns := make([]uint64, 0, fieldEventsLen)
   501  	for _, s := range ss {
   502  		n, err := strconv.ParseUint(s, 10, 64)
   503  		if err != nil {
   504  			return nil, err
   505  		}
   506  
   507  		ns = append(ns, n)
   508  	}
   509  
   510  	return &NFSEventsStats{
   511  		InodeRevalidate:     ns[0],
   512  		DnodeRevalidate:     ns[1],
   513  		DataInvalidate:      ns[2],
   514  		AttributeInvalidate: ns[3],
   515  		VFSOpen:             ns[4],
   516  		VFSLookup:           ns[5],
   517  		VFSAccess:           ns[6],
   518  		VFSUpdatePage:       ns[7],
   519  		VFSReadPage:         ns[8],
   520  		VFSReadPages:        ns[9],
   521  		VFSWritePage:        ns[10],
   522  		VFSWritePages:       ns[11],
   523  		VFSGetdents:         ns[12],
   524  		VFSSetattr:          ns[13],
   525  		VFSFlush:            ns[14],
   526  		VFSFsync:            ns[15],
   527  		VFSLock:             ns[16],
   528  		VFSFileRelease:      ns[17],
   529  		CongestionWait:      ns[18],
   530  		Truncation:          ns[19],
   531  		WriteExtension:      ns[20],
   532  		SillyRename:         ns[21],
   533  		ShortRead:           ns[22],
   534  		ShortWrite:          ns[23],
   535  		JukeboxDelay:        ns[24],
   536  		PNFSRead:            ns[25],
   537  		PNFSWrite:           ns[26],
   538  	}, nil
   539  }
   540  
   541  // parseNFSOperationStats parses a slice of NFSOperationStats by scanning
   542  // additional information about per-operation statistics until an empty
   543  // line is reached.
   544  func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
   545  	const (
   546  		// Minimum number of expected fields in each per-operation statistics set
   547  		minFields = 9
   548  	)
   549  
   550  	var ops []NFSOperationStats
   551  
   552  	for s.Scan() {
   553  		ss := strings.Fields(string(s.Bytes()))
   554  		if len(ss) == 0 {
   555  			// Must break when reading a blank line after per-operation stats to
   556  			// enable top-level function to parse the next device entry
   557  			break
   558  		}
   559  
   560  		if len(ss) < minFields {
   561  			return nil, fmt.Errorf("%w: invalid NFS per-operations stats: %v", ErrFileParse, ss)
   562  		}
   563  
   564  		// Skip string operation name for integers
   565  		ns := make([]uint64, 0, minFields-1)
   566  		for _, st := range ss[1:] {
   567  			n, err := strconv.ParseUint(st, 10, 64)
   568  			if err != nil {
   569  				return nil, err
   570  			}
   571  
   572  			ns = append(ns, n)
   573  		}
   574  		opStats := NFSOperationStats{
   575  			Operation:                           strings.TrimSuffix(ss[0], ":"),
   576  			Requests:                            ns[0],
   577  			Transmissions:                       ns[1],
   578  			MajorTimeouts:                       ns[2],
   579  			BytesSent:                           ns[3],
   580  			BytesReceived:                       ns[4],
   581  			CumulativeQueueMilliseconds:         ns[5],
   582  			CumulativeTotalResponseMilliseconds: ns[6],
   583  			CumulativeTotalRequestMilliseconds:  ns[7],
   584  		}
   585  		if ns[0] != 0 {
   586  			opStats.AverageRTTMilliseconds = float64(ns[6]) / float64(ns[0])
   587  		}
   588  
   589  		if len(ns) > 8 {
   590  			opStats.Errors = ns[8]
   591  		}
   592  
   593  		ops = append(ops, opStats)
   594  	}
   595  
   596  	return ops, s.Err()
   597  }
   598  
   599  // parseNFSTransportStats parses a NFSTransportStats line using an input set of
   600  // integer fields matched to a specific stats version.
   601  func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
   602  	// Extract the protocol field. It is the only string value in the line
   603  	protocol := ss[0]
   604  	ss = ss[1:]
   605  
   606  	switch statVersion {
   607  	case statVersion10:
   608  		var expectedLength int
   609  		if protocol == "tcp" {
   610  			expectedLength = fieldTransport10TCPLen
   611  		} else if protocol == "udp" {
   612  			expectedLength = fieldTransport10UDPLen
   613  		} else {
   614  			return nil, fmt.Errorf("%w: Invalid NFS protocol \"%s\" in stats 1.0 statement: %v", ErrFileParse, protocol, ss)
   615  		}
   616  		if len(ss) != expectedLength {
   617  			return nil, fmt.Errorf("%w: Invalid NFS transport stats 1.0 statement: %v", ErrFileParse, ss)
   618  		}
   619  	case statVersion11:
   620  		var expectedLength int
   621  		if protocol == "tcp" {
   622  			expectedLength = fieldTransport11TCPLen
   623  		} else if protocol == "udp" {
   624  			expectedLength = fieldTransport11UDPLen
   625  		} else if protocol == "rdma" {
   626  			expectedLength = fieldTransport11RDMAMinLen
   627  		} else {
   628  			return nil, fmt.Errorf("%w: invalid NFS protocol \"%s\" in stats 1.1 statement: %v", ErrFileParse, protocol, ss)
   629  		}
   630  		if (len(ss) != expectedLength && (protocol == "tcp" || protocol == "udp")) ||
   631  			(protocol == "rdma" && len(ss) < expectedLength) {
   632  			return nil, fmt.Errorf("%w: invalid NFS transport stats 1.1 statement: %v, protocol: %v", ErrFileParse, ss, protocol)
   633  		}
   634  	default:
   635  		return nil, fmt.Errorf("%s: Unrecognized NFS transport stats version: %q, protocol: %v", ErrFileParse, statVersion, protocol)
   636  	}
   637  
   638  	// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
   639  	// in a v1.0 response. Since the stat length is bigger for TCP stats, we use
   640  	// the TCP length here.
   641  	//
   642  	// Note: slice length must be set to length of v1.1 stats to avoid a panic when
   643  	// only v1.0 stats are present.
   644  	// See: https://github.com/prometheus/node_exporter/issues/571.
   645  	//
   646  	// Note: NFS Over RDMA slice length is fieldTransport11RDMAMaxLen
   647  	ns := make([]uint64, fieldTransport11RDMAMaxLen+3)
   648  	for i, s := range ss {
   649  		n, err := strconv.ParseUint(s, 10, 64)
   650  		if err != nil {
   651  			return nil, err
   652  		}
   653  
   654  		ns[i] = n
   655  	}
   656  
   657  	// The fields differ depending on the transport protocol (TCP or UDP)
   658  	// From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt
   659  	//
   660  	// For the udp RPC transport there is no connection count, connect idle time,
   661  	// or idle time (fields #3, #4, and #5); all other fields are the same. So
   662  	// we set them to 0 here.
   663  	if protocol == "udp" {
   664  		ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
   665  	} else if protocol == "tcp" {
   666  		ns = append(ns[:fieldTransport11TCPLen], make([]uint64, fieldTransport11RDMAMaxLen-fieldTransport11TCPLen+3)...)
   667  	} else if protocol == "rdma" {
   668  		ns = append(ns[:fieldTransport10TCPLen], append(make([]uint64, 3), ns[fieldTransport10TCPLen:]...)...)
   669  	}
   670  
   671  	return &NFSTransportStats{
   672  		// NFS xprt over tcp or udp
   673  		Protocol:                 protocol,
   674  		Port:                     ns[0],
   675  		Bind:                     ns[1],
   676  		Connect:                  ns[2],
   677  		ConnectIdleTime:          ns[3],
   678  		IdleTimeSeconds:          ns[4],
   679  		Sends:                    ns[5],
   680  		Receives:                 ns[6],
   681  		BadTransactionIDs:        ns[7],
   682  		CumulativeActiveRequests: ns[8],
   683  		CumulativeBacklog:        ns[9],
   684  
   685  		// NFS xprt over tcp or udp
   686  		// And statVersion 1.1
   687  		MaximumRPCSlotsUsed:    ns[10],
   688  		CumulativeSendingQueue: ns[11],
   689  		CumulativePendingQueue: ns[12],
   690  
   691  		// NFS xprt over rdma
   692  		// And stat Version 1.1
   693  		ReadChunkCount:       ns[13],
   694  		WriteChunkCount:      ns[14],
   695  		ReplyChunkCount:      ns[15],
   696  		TotalRdmaRequest:     ns[16],
   697  		PullupCopyCount:      ns[17],
   698  		HardwayRegisterCount: ns[18],
   699  		FailedMarshalCount:   ns[19],
   700  		BadReplyCount:        ns[20],
   701  		MrsRecovered:         ns[21],
   702  		MrsOrphaned:          ns[22],
   703  		MrsAllocated:         ns[23],
   704  		EmptySendctxQ:        ns[24],
   705  		TotalRdmaReply:       ns[25],
   706  		FixupCopyCount:       ns[26],
   707  		ReplyWaitsForSend:    ns[27],
   708  		LocalInvNeeded:       ns[28],
   709  		NomsgCallCount:       ns[29],
   710  		BcallCount:           ns[30],
   711  	}, nil
   712  }
   713  

View as plain text