From bae238be61726ee2241c0e2b645be1a883da9b93 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Wed, 7 Dec 2016 12:19:16 -0500 Subject: [PATCH 1/2] Update github.com/prometheus/procfs to latest revision --- .../github.com/prometheus/procfs/AUTHORS.md | 1 + .../prometheus/procfs/mountstats.go | 552 ++++++++++++++++++ vendor/github.com/prometheus/procfs/proc.go | 16 +- .../prometheus/procfs/proc_limits.go | 2 +- vendor/vendor.json | 5 +- 5 files changed, 571 insertions(+), 5 deletions(-) create mode 100644 vendor/github.com/prometheus/procfs/mountstats.go diff --git a/vendor/github.com/prometheus/procfs/AUTHORS.md b/vendor/github.com/prometheus/procfs/AUTHORS.md index 0c802dd8..d5586356 100644 --- a/vendor/github.com/prometheus/procfs/AUTHORS.md +++ b/vendor/github.com/prometheus/procfs/AUTHORS.md @@ -14,6 +14,7 @@ The following individuals have contributed code to this repository * Ji-Hoon, Seol * Jonas Große Sundrup * Julius Volz +* Matt Layher * Matthias Rampke * Nicky Gerritsen * Rémi Audebert diff --git a/vendor/github.com/prometheus/procfs/mountstats.go b/vendor/github.com/prometheus/procfs/mountstats.go new file mode 100644 index 00000000..3b7d3ec8 --- /dev/null +++ b/vendor/github.com/prometheus/procfs/mountstats.go @@ -0,0 +1,552 @@ +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 +} diff --git a/vendor/github.com/prometheus/procfs/proc.go b/vendor/github.com/prometheus/procfs/proc.go index deeab173..8717e1fe 100644 --- a/vendor/github.com/prometheus/procfs/proc.go +++ b/vendor/github.com/prometheus/procfs/proc.go @@ -41,7 +41,7 @@ func NewProc(pid int) (Proc, error) { return fs.NewProc(pid) } -// AllProcs returns a list of all currently avaible processes under /proc. +// AllProcs returns a list of all currently available processes under /proc. func AllProcs() (Procs, error) { fs, err := NewFS(DefaultMountPoint) if err != nil { @@ -71,7 +71,7 @@ func (fs FS) NewProc(pid int) (Proc, error) { return Proc{PID: pid, fs: fs}, nil } -// AllProcs returns a list of all currently avaible processes. +// AllProcs returns a list of all currently available processes. func (fs FS) AllProcs() (Procs, error) { d, err := os.Open(fs.Path()) if err != nil { @@ -192,6 +192,18 @@ func (p Proc) FileDescriptorsLen() (int, error) { return len(fds), nil } +// MountStats retrieves statistics and configuration for mount points in a +// process's namespace. +func (p Proc) MountStats() ([]*Mount, error) { + f, err := os.Open(p.path("mountstats")) + if err != nil { + return nil, err + } + defer f.Close() + + return parseMountStats(f) +} + func (p Proc) fileDescriptors() ([]string, error) { d, err := os.Open(p.path("fd")) if err != nil { diff --git a/vendor/github.com/prometheus/procfs/proc_limits.go b/vendor/github.com/prometheus/procfs/proc_limits.go index acac02bb..2df997ce 100644 --- a/vendor/github.com/prometheus/procfs/proc_limits.go +++ b/vendor/github.com/prometheus/procfs/proc_limits.go @@ -87,7 +87,7 @@ func (p Proc) NewLimits() (ProcLimits, error) { case "Max cpu time": l.CPUTime, err = parseInt(fields[1]) case "Max file size": - l.FileLocks, err = parseInt(fields[1]) + l.FileSize, err = parseInt(fields[1]) case "Max data size": l.DataSize, err = parseInt(fields[1]) case "Max stack size": diff --git a/vendor/vendor.json b/vendor/vendor.json index 9d394a99..1963825c 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -80,9 +80,10 @@ "revisionTime": "2016-05-03T22:05:32Z" }, { + "checksumSHA1": "L+p4t3KrLDAKJnrreOz2BZIt9Mg=", "path": "github.com/prometheus/procfs", - "revision": "406e5b7bfd8201a36e2bb5f7bdae0b03380c2ce8", - "revisionTime": "2015-10-29T15:50:50-04:00" + "revision": "fcdb11ccb4389efb1b210b7ffb623ab71c5fdd60", + "revisionTime": "2016-12-06T22:21:41Z" }, { "path": "github.com/soundcloud/go-runit/runit", From 2c93e7af0f1c79709e40556170aecf805da1795f Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Wed, 7 Dec 2016 12:20:10 -0500 Subject: [PATCH 2/2] Add Matt Layher to AUTHORS --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 06e6bb39..84944206 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -26,6 +26,7 @@ The following individuals have contributed code to this repository * Jonas Große Sundrup * Julius Volz * Ken Herner +* Matt Layher * Matthias Rampke * Siavash Safi * Stephen Shirley