cubefs
638 строк · 18.6 Кб
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
14package procfs15
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
23import (24"bufio"25"fmt"26"io"27"strconv"28"strings"29"time"30)
31
32// Constants shared between multiple functions.
33const (34deviceEntryLen = 835
36fieldBytesLen = 837fieldEventsLen = 2738
39statVersion10 = "1.0"40statVersion11 = "1.1"41
42fieldTransport10TCPLen = 1043fieldTransport10UDPLen = 744
45fieldTransport11TCPLen = 1346fieldTransport11UDPLen = 1047)
48
49// A Mount is a device mount parsed from /proc/[pid]/mountstats.
50type Mount struct {51// Name of the device.52Device string53// The mount point of the device.54Mount string55// The filesystem type used by the device.56Type string57// If available additional statistics related to this Mount.58// Use a type assertion to determine if additional statistics are available.59Stats MountStats
60}
61
62// A MountStats is a type which contains detailed statistics for a specific
63// type of Mount.
64type MountStats interface {65mountStats()66}
67
68// A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
69type MountStatsNFS struct {70// The version of statistics provided.71StatVersion string72// The mount options of the NFS mount.73Opts map[string]string74// The age of the NFS mount.75Age time.Duration76// Statistics related to byte counters for various operations.77Bytes NFSBytesStats
78// Statistics related to various NFS event occurrences.79Events NFSEventsStats
80// Statistics broken down by filesystem operation.81Operations []NFSOperationStats82// Statistics about the NFS RPC transport.83Transport NFSTransportStats
84}
85
86// mountStats implements MountStats.
87func (m MountStatsNFS) mountStats() {}88
89// A NFSBytesStats contains statistics about the number of bytes read and written
90// by an NFS client to and from an NFS server.
91type NFSBytesStats struct {92// Number of bytes read using the read() syscall.93Read uint6494// Number of bytes written using the write() syscall.95Write uint6496// Number of bytes read using the read() syscall in O_DIRECT mode.97DirectRead uint6498// Number of bytes written using the write() syscall in O_DIRECT mode.99DirectWrite uint64100// Number of bytes read from the NFS server, in total.101ReadTotal uint64102// Number of bytes written to the NFS server, in total.103WriteTotal uint64104// Number of pages read directly via mmap()'d files.105ReadPages uint64106// Number of pages written directly via mmap()'d files.107WritePages uint64108}
109
110// A NFSEventsStats contains statistics about NFS event occurrences.
111type NFSEventsStats struct {112// Number of times cached inode attributes are re-validated from the server.113InodeRevalidate uint64114// Number of times cached dentry nodes are re-validated from the server.115DnodeRevalidate uint64116// Number of times an inode cache is cleared.117DataInvalidate uint64118// Number of times cached inode attributes are invalidated.119AttributeInvalidate uint64120// Number of times files or directories have been open()'d.121VFSOpen uint64122// Number of times a directory lookup has occurred.123VFSLookup uint64124// Number of times permissions have been checked.125VFSAccess uint64126// Number of updates (and potential writes) to pages.127VFSUpdatePage uint64128// Number of pages read directly via mmap()'d files.129VFSReadPage uint64130// Number of times a group of pages have been read.131VFSReadPages uint64132// Number of pages written directly via mmap()'d files.133VFSWritePage uint64134// Number of times a group of pages have been written.135VFSWritePages uint64136// Number of times directory entries have been read with getdents().137VFSGetdents uint64138// Number of times attributes have been set on inodes.139VFSSetattr uint64140// Number of pending writes that have been forcefully flushed to the server.141VFSFlush uint64142// Number of times fsync() has been called on directories and files.143VFSFsync uint64144// Number of times locking has been attempted on a file.145VFSLock uint64146// Number of times files have been closed and released.147VFSFileRelease uint64148// Unknown. Possibly unused.149CongestionWait uint64150// Number of times files have been truncated.151Truncation uint64152// Number of times a file has been grown due to writes beyond its existing end.153WriteExtension uint64154// Number of times a file was removed while still open by another process.155SillyRename uint64156// Number of times the NFS server gave less data than expected while reading.157ShortRead uint64158// Number of times the NFS server wrote less data than expected while writing.159ShortWrite uint64160// Number of times the NFS server indicated EJUKEBOX; retrieving data from161// offline storage.162JukeboxDelay uint64163// Number of NFS v4.1+ pNFS reads.164PNFSRead uint64165// Number of NFS v4.1+ pNFS writes.166PNFSWrite uint64167}
168
169// A NFSOperationStats contains statistics for a single operation.
170type NFSOperationStats struct {171// The name of the operation.172Operation string173// Number of requests performed for this operation.174Requests uint64175// Number of times an actual RPC request has been transmitted for this operation.176Transmissions uint64177// Number of times a request has had a major timeout.178MajorTimeouts uint64179// Number of bytes sent for this operation, including RPC headers and payload.180BytesSent uint64181// Number of bytes received for this operation, including RPC headers and payload.182BytesReceived uint64183// Duration all requests spent queued for transmission before they were sent.184CumulativeQueueMilliseconds uint64185// Duration it took to get a reply back after the request was transmitted.186CumulativeTotalResponseMilliseconds uint64187// Duration from when a request was enqueued to when it was completely handled.188CumulativeTotalRequestMilliseconds uint64189// The count of operations that complete with tk_status < 0. These statuses usually indicate error conditions.190Errors uint64191}
192
193// A NFSTransportStats contains statistics for the NFS mount RPC requests and
194// responses.
195type NFSTransportStats struct {196// The transport protocol used for the NFS mount.197Protocol string198// The local port used for the NFS mount.199Port uint64200// Number of times the client has had to establish a connection from scratch201// to the NFS server.202Bind uint64203// Number of times the client has made a TCP connection to the NFS server.204Connect uint64205// Duration (in jiffies, a kernel internal unit of time) the NFS mount has206// spent waiting for connections to the server to be established.207ConnectIdleTime uint64208// Duration since the NFS mount last saw any RPC traffic.209IdleTimeSeconds uint64210// Number of RPC requests for this mount sent to the NFS server.211Sends uint64212// Number of RPC responses for this mount received from the NFS server.213Receives uint64214// Number of times the NFS server sent a response with a transaction ID215// unknown to this client.216BadTransactionIDs uint64217// A running counter, incremented on each request as the current difference218// ebetween sends and receives.219CumulativeActiveRequests uint64220// A running counter, incremented on each request by the current backlog221// queue size.222CumulativeBacklog uint64223
224// Stats below only available with stat version 1.1.225
226// Maximum number of simultaneously active RPC requests ever used.227MaximumRPCSlotsUsed uint64228// A running counter, incremented on each request as the current size of the229// sending queue.230CumulativeSendingQueue uint64231// A running counter, incremented on each request as the current size of the232// pending queue.233CumulativePendingQueue uint64234}
235
236// parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
237// of Mount structures containing detailed information about each mount.
238// If available, statistics for each mount are parsed as well.
239func parseMountStats(r io.Reader) ([]*Mount, error) {240const (241device = "device"242statVersionPrefix = "statvers="243
244nfs3Type = "nfs"245nfs4Type = "nfs4"246)247
248var mounts []*Mount249
250s := bufio.NewScanner(r)251for s.Scan() {252// Only look for device entries in this function253ss := strings.Fields(string(s.Bytes()))254if len(ss) == 0 || ss[0] != device {255continue256}257
258m, err := parseMount(ss)259if err != nil {260return nil, err261}262
263// Does this mount also possess statistics information?264if len(ss) > deviceEntryLen {265// Only NFSv3 and v4 are supported for parsing statistics266if m.Type != nfs3Type && m.Type != nfs4Type {267return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)268}269
270statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)271
272stats, err := parseMountStatsNFS(s, statVersion)273if err != nil {274return nil, err275}276
277m.Stats = stats278}279
280mounts = append(mounts, m)281}282
283return mounts, s.Err()284}
285
286// parseMount parses an entry in /proc/[pid]/mountstats in the format:
287// device [device] mounted on [mount] with fstype [type]
288func parseMount(ss []string) (*Mount, error) {289if len(ss) < deviceEntryLen {290return nil, fmt.Errorf("invalid device entry: %v", ss)291}292
293// Check for specific words appearing at specific indices to ensure294// the format is consistent with what we expect295format := []struct {296i int297s string298}{299{i: 0, s: "device"},300{i: 2, s: "mounted"},301{i: 3, s: "on"},302{i: 5, s: "with"},303{i: 6, s: "fstype"},304}305
306for _, f := range format {307if ss[f.i] != f.s {308return nil, fmt.Errorf("invalid device entry: %v", ss)309}310}311
312return &Mount{313Device: ss[1],314Mount: ss[4],315Type: ss[7],316}, nil317}
318
319// parseMountStatsNFS parses a MountStatsNFS by scanning additional information
320// related to NFS statistics.
321func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {322// Field indicators for parsing specific types of data323const (324fieldOpts = "opts:"325fieldAge = "age:"326fieldBytes = "bytes:"327fieldEvents = "events:"328fieldPerOpStats = "per-op"329fieldTransport = "xprt:"330)331
332stats := &MountStatsNFS{333StatVersion: statVersion,334}335
336for s.Scan() {337ss := strings.Fields(string(s.Bytes()))338if len(ss) == 0 {339break340}341
342switch ss[0] {343case fieldOpts:344if len(ss) < 2 {345return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)346}347if stats.Opts == nil {348stats.Opts = map[string]string{}349}350for _, opt := range strings.Split(ss[1], ",") {351split := strings.Split(opt, "=")352if len(split) == 2 {353stats.Opts[split[0]] = split[1]354} else {355stats.Opts[opt] = ""356}357}358case fieldAge:359if len(ss) < 2 {360return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)361}362// Age integer is in seconds363d, err := time.ParseDuration(ss[1] + "s")364if err != nil {365return nil, err366}367
368stats.Age = d369case fieldBytes:370if len(ss) < 2 {371return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)372}373bstats, err := parseNFSBytesStats(ss[1:])374if err != nil {375return nil, err376}377
378stats.Bytes = *bstats379case fieldEvents:380if len(ss) < 2 {381return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)382}383estats, err := parseNFSEventsStats(ss[1:])384if err != nil {385return nil, err386}387
388stats.Events = *estats389case fieldTransport:390if len(ss) < 3 {391return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)392}393
394tstats, err := parseNFSTransportStats(ss[1:], statVersion)395if err != nil {396return nil, err397}398
399stats.Transport = *tstats400}401
402// When encountering "per-operation statistics", we must break this403// loop and parse them separately to ensure we can terminate parsing404// before reaching another device entry; hence why this 'if' statement405// is not just another switch case406if ss[0] == fieldPerOpStats {407break408}409}410
411if err := s.Err(); err != nil {412return nil, err413}414
415// NFS per-operation stats appear last before the next device entry416perOpStats, err := parseNFSOperationStats(s)417if err != nil {418return nil, err419}420
421stats.Operations = perOpStats422
423return stats, nil424}
425
426// parseNFSBytesStats parses a NFSBytesStats line using an input set of
427// integer fields.
428func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {429if len(ss) != fieldBytesLen {430return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)431}432
433ns := make([]uint64, 0, fieldBytesLen)434for _, s := range ss {435n, err := strconv.ParseUint(s, 10, 64)436if err != nil {437return nil, err438}439
440ns = append(ns, n)441}442
443return &NFSBytesStats{444Read: ns[0],445Write: ns[1],446DirectRead: ns[2],447DirectWrite: ns[3],448ReadTotal: ns[4],449WriteTotal: ns[5],450ReadPages: ns[6],451WritePages: ns[7],452}, nil453}
454
455// parseNFSEventsStats parses a NFSEventsStats line using an input set of
456// integer fields.
457func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {458if len(ss) != fieldEventsLen {459return nil, fmt.Errorf("invalid NFS events stats: %v", ss)460}461
462ns := make([]uint64, 0, fieldEventsLen)463for _, s := range ss {464n, err := strconv.ParseUint(s, 10, 64)465if err != nil {466return nil, err467}468
469ns = append(ns, n)470}471
472return &NFSEventsStats{473InodeRevalidate: ns[0],474DnodeRevalidate: ns[1],475DataInvalidate: ns[2],476AttributeInvalidate: ns[3],477VFSOpen: ns[4],478VFSLookup: ns[5],479VFSAccess: ns[6],480VFSUpdatePage: ns[7],481VFSReadPage: ns[8],482VFSReadPages: ns[9],483VFSWritePage: ns[10],484VFSWritePages: ns[11],485VFSGetdents: ns[12],486VFSSetattr: ns[13],487VFSFlush: ns[14],488VFSFsync: ns[15],489VFSLock: ns[16],490VFSFileRelease: ns[17],491CongestionWait: ns[18],492Truncation: ns[19],493WriteExtension: ns[20],494SillyRename: ns[21],495ShortRead: ns[22],496ShortWrite: ns[23],497JukeboxDelay: ns[24],498PNFSRead: ns[25],499PNFSWrite: ns[26],500}, nil501}
502
503// parseNFSOperationStats parses a slice of NFSOperationStats by scanning
504// additional information about per-operation statistics until an empty
505// line is reached.
506func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {507const (508// Minimum number of expected fields in each per-operation statistics set509minFields = 9510)511
512var ops []NFSOperationStats513
514for s.Scan() {515ss := strings.Fields(string(s.Bytes()))516if len(ss) == 0 {517// Must break when reading a blank line after per-operation stats to518// enable top-level function to parse the next device entry519break520}521
522if len(ss) < minFields {523return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)524}525
526// Skip string operation name for integers527ns := make([]uint64, 0, minFields-1)528for _, st := range ss[1:] {529n, err := strconv.ParseUint(st, 10, 64)530if err != nil {531return nil, err532}533
534ns = append(ns, n)535}536
537opStats := NFSOperationStats{538Operation: strings.TrimSuffix(ss[0], ":"),539Requests: ns[0],540Transmissions: ns[1],541MajorTimeouts: ns[2],542BytesSent: ns[3],543BytesReceived: ns[4],544CumulativeQueueMilliseconds: ns[5],545CumulativeTotalResponseMilliseconds: ns[6],546CumulativeTotalRequestMilliseconds: ns[7],547}548
549if len(ns) > 8 {550opStats.Errors = ns[8]551}552
553ops = append(ops, opStats)554}555
556return ops, s.Err()557}
558
559// parseNFSTransportStats parses a NFSTransportStats line using an input set of
560// integer fields matched to a specific stats version.
561func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {562// Extract the protocol field. It is the only string value in the line563protocol := ss[0]564ss = ss[1:]565
566switch statVersion {567case statVersion10:568var expectedLength int569if protocol == "tcp" {570expectedLength = fieldTransport10TCPLen571} else if protocol == "udp" {572expectedLength = fieldTransport10UDPLen573} else {574return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.0 statement: %v", protocol, ss)575}576if len(ss) != expectedLength {577return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)578}579case statVersion11:580var expectedLength int581if protocol == "tcp" {582expectedLength = fieldTransport11TCPLen583} else if protocol == "udp" {584expectedLength = fieldTransport11UDPLen585} else {586return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.1 statement: %v", protocol, ss)587}588if len(ss) != expectedLength {589return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)590}591default:592return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion)593}594
595// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay596// in a v1.0 response. Since the stat length is bigger for TCP stats, we use597// the TCP length here.598//599// Note: slice length must be set to length of v1.1 stats to avoid a panic when600// only v1.0 stats are present.601// See: https://github.com/prometheus/node_exporter/issues/571.602ns := make([]uint64, fieldTransport11TCPLen)603for i, s := range ss {604n, err := strconv.ParseUint(s, 10, 64)605if err != nil {606return nil, err607}608
609ns[i] = n610}611
612// The fields differ depending on the transport protocol (TCP or UDP)613// From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt614//615// For the udp RPC transport there is no connection count, connect idle time,616// or idle time (fields #3, #4, and #5); all other fields are the same. So617// we set them to 0 here.618if protocol == "udp" {619ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)620}621
622return &NFSTransportStats{623Protocol: protocol,624Port: ns[0],625Bind: ns[1],626Connect: ns[2],627ConnectIdleTime: ns[3],628IdleTimeSeconds: ns[4],629Sends: ns[5],630Receives: ns[6],631BadTransactionIDs: ns[7],632CumulativeActiveRequests: ns[8],633CumulativeBacklog: ns[9],634MaximumRPCSlotsUsed: ns[10],635CumulativeSendingQueue: ns[11],636CumulativePendingQueue: ns[12],637}, nil638}
639