Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / prometheus / procfs / mountstats.go
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
49 // A Mount is a device mount parsed from /proc/[pid]/mountstats.
50 type Mount struct {
51         // Name of the device.
52         Device string
53         // The mount point of the device.
54         Mount string
55         // The filesystem type used by the device.
56         Type string
57         // If available additional statistics related to this Mount.
58         // Use a type assertion to determine if additional statistics are available.
59         Stats MountStats
60 }
61
62 // A MountStats is a type which contains detailed statistics for a specific
63 // type of Mount.
64 type MountStats interface {
65         mountStats()
66 }
67
68 // A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
69 type MountStatsNFS struct {
70         // The version of statistics provided.
71         StatVersion string
72         // The optional mountaddr of the NFS mount.
73         MountAddress string
74         // The age of the NFS mount.
75         Age time.Duration
76         // Statistics related to byte counters for various operations.
77         Bytes NFSBytesStats
78         // Statistics related to various NFS event occurrences.
79         Events NFSEventsStats
80         // Statistics broken down by filesystem operation.
81         Operations []NFSOperationStats
82         // Statistics about the NFS RPC transport.
83         Transport NFSTransportStats
84 }
85
86 // mountStats implements MountStats.
87 func (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.
91 type NFSBytesStats struct {
92         // Number of bytes read using the read() syscall.
93         Read uint64
94         // Number of bytes written using the write() syscall.
95         Write uint64
96         // Number of bytes read using the read() syscall in O_DIRECT mode.
97         DirectRead uint64
98         // Number of bytes written using the write() syscall in O_DIRECT mode.
99         DirectWrite uint64
100         // Number of bytes read from the NFS server, in total.
101         ReadTotal uint64
102         // Number of bytes written to the NFS server, in total.
103         WriteTotal uint64
104         // Number of pages read directly via mmap()'d files.
105         ReadPages uint64
106         // Number of pages written directly via mmap()'d files.
107         WritePages uint64
108 }
109
110 // A NFSEventsStats contains statistics about NFS event occurrences.
111 type NFSEventsStats struct {
112         // Number of times cached inode attributes are re-validated from the server.
113         InodeRevalidate uint64
114         // Number of times cached dentry nodes are re-validated from the server.
115         DnodeRevalidate uint64
116         // Number of times an inode cache is cleared.
117         DataInvalidate uint64
118         // Number of times cached inode attributes are invalidated.
119         AttributeInvalidate uint64
120         // Number of times files or directories have been open()'d.
121         VFSOpen uint64
122         // Number of times a directory lookup has occurred.
123         VFSLookup uint64
124         // Number of times permissions have been checked.
125         VFSAccess uint64
126         // Number of updates (and potential writes) to pages.
127         VFSUpdatePage uint64
128         // Number of pages read directly via mmap()'d files.
129         VFSReadPage uint64
130         // Number of times a group of pages have been read.
131         VFSReadPages uint64
132         // Number of pages written directly via mmap()'d files.
133         VFSWritePage uint64
134         // Number of times a group of pages have been written.
135         VFSWritePages uint64
136         // Number of times directory entries have been read with getdents().
137         VFSGetdents uint64
138         // Number of times attributes have been set on inodes.
139         VFSSetattr uint64
140         // Number of pending writes that have been forcefully flushed to the server.
141         VFSFlush uint64
142         // Number of times fsync() has been called on directories and files.
143         VFSFsync uint64
144         // Number of times locking has been attempted on a file.
145         VFSLock uint64
146         // Number of times files have been closed and released.
147         VFSFileRelease uint64
148         // Unknown.  Possibly unused.
149         CongestionWait uint64
150         // Number of times files have been truncated.
151         Truncation uint64
152         // Number of times a file has been grown due to writes beyond its existing end.
153         WriteExtension uint64
154         // Number of times a file was removed while still open by another process.
155         SillyRename uint64
156         // Number of times the NFS server gave less data than expected while reading.
157         ShortRead uint64
158         // Number of times the NFS server wrote less data than expected while writing.
159         ShortWrite uint64
160         // Number of times the NFS server indicated EJUKEBOX; retrieving data from
161         // offline storage.
162         JukeboxDelay uint64
163         // Number of NFS v4.1+ pNFS reads.
164         PNFSRead uint64
165         // Number of NFS v4.1+ pNFS writes.
166         PNFSWrite uint64
167 }
168
169 // A NFSOperationStats contains statistics for a single operation.
170 type NFSOperationStats struct {
171         // The name of the operation.
172         Operation string
173         // Number of requests performed for this operation.
174         Requests uint64
175         // Number of times an actual RPC request has been transmitted for this operation.
176         Transmissions uint64
177         // Number of times a request has had a major timeout.
178         MajorTimeouts uint64
179         // Number of bytes sent for this operation, including RPC headers and payload.
180         BytesSent uint64
181         // Number of bytes received for this operation, including RPC headers and payload.
182         BytesReceived uint64
183         // Duration all requests spent queued for transmission before they were sent.
184         CumulativeQueueTime time.Duration
185         // Duration it took to get a reply back after the request was transmitted.
186         CumulativeTotalResponseTime time.Duration
187         // Duration from when a request was enqueued to when it was completely handled.
188         CumulativeTotalRequestTime time.Duration
189 }
190
191 // A NFSTransportStats contains statistics for the NFS mount RPC requests and
192 // responses.
193 type NFSTransportStats struct {
194         // The transport protocol used for the NFS mount.
195         Protocol string
196         // The local port used for the NFS mount.
197         Port uint64
198         // Number of times the client has had to establish a connection from scratch
199         // to the NFS server.
200         Bind uint64
201         // Number of times the client has made a TCP connection to the NFS server.
202         Connect uint64
203         // Duration (in jiffies, a kernel internal unit of time) the NFS mount has
204         // spent waiting for connections to the server to be established.
205         ConnectIdleTime uint64
206         // Duration since the NFS mount last saw any RPC traffic.
207         IdleTime time.Duration
208         // Number of RPC requests for this mount sent to the NFS server.
209         Sends uint64
210         // Number of RPC responses for this mount received from the NFS server.
211         Receives uint64
212         // Number of times the NFS server sent a response with a transaction ID
213         // unknown to this client.
214         BadTransactionIDs uint64
215         // A running counter, incremented on each request as the current difference
216         // ebetween sends and receives.
217         CumulativeActiveRequests uint64
218         // A running counter, incremented on each request by the current backlog
219         // queue size.
220         CumulativeBacklog uint64
221
222         // Stats below only available with stat version 1.1.
223
224         // Maximum number of simultaneously active RPC requests ever used.
225         MaximumRPCSlotsUsed uint64
226         // A running counter, incremented on each request as the current size of the
227         // sending queue.
228         CumulativeSendingQueue uint64
229         // A running counter, incremented on each request as the current size of the
230         // pending queue.
231         CumulativePendingQueue uint64
232 }
233
234 // parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
235 // of Mount structures containing detailed information about each mount.
236 // If available, statistics for each mount are parsed as well.
237 func parseMountStats(r io.Reader) ([]*Mount, error) {
238         const (
239                 device            = "device"
240                 statVersionPrefix = "statvers="
241
242                 nfs3Type = "nfs"
243                 nfs4Type = "nfs4"
244         )
245
246         var mounts []*Mount
247
248         s := bufio.NewScanner(r)
249         for s.Scan() {
250                 // Only look for device entries in this function
251                 ss := strings.Fields(string(s.Bytes()))
252                 if len(ss) == 0 || ss[0] != device {
253                         continue
254                 }
255
256                 m, err := parseMount(ss)
257                 if err != nil {
258                         return nil, err
259                 }
260
261                 // Does this mount also possess statistics information?
262                 if len(ss) > deviceEntryLen {
263                         // Only NFSv3 and v4 are supported for parsing statistics
264                         if m.Type != nfs3Type && m.Type != nfs4Type {
265                                 return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)
266                         }
267
268                         statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
269
270                         stats, err := parseMountStatsNFS(s, statVersion)
271                         if err != nil {
272                                 return nil, err
273                         }
274
275                         m.Stats = stats
276                 }
277
278                 mounts = append(mounts, m)
279         }
280
281         return mounts, s.Err()
282 }
283
284 // parseMount parses an entry in /proc/[pid]/mountstats in the format:
285 //   device [device] mounted on [mount] with fstype [type]
286 func parseMount(ss []string) (*Mount, error) {
287         if len(ss) < deviceEntryLen {
288                 return nil, fmt.Errorf("invalid device entry: %v", ss)
289         }
290
291         // Check for specific words appearing at specific indices to ensure
292         // the format is consistent with what we expect
293         format := []struct {
294                 i int
295                 s string
296         }{
297                 {i: 0, s: "device"},
298                 {i: 2, s: "mounted"},
299                 {i: 3, s: "on"},
300                 {i: 5, s: "with"},
301                 {i: 6, s: "fstype"},
302         }
303
304         for _, f := range format {
305                 if ss[f.i] != f.s {
306                         return nil, fmt.Errorf("invalid device entry: %v", ss)
307                 }
308         }
309
310         return &Mount{
311                 Device: ss[1],
312                 Mount:  ss[4],
313                 Type:   ss[7],
314         }, nil
315 }
316
317 // parseMountStatsNFS parses a MountStatsNFS by scanning additional information
318 // related to NFS statistics.
319 func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
320         // Field indicators for parsing specific types of data
321         const (
322                 fieldOpts       = "opts:"
323                 fieldAge        = "age:"
324                 fieldBytes      = "bytes:"
325                 fieldEvents     = "events:"
326                 fieldPerOpStats = "per-op"
327                 fieldTransport  = "xprt:"
328         )
329
330         stats := &MountStatsNFS{
331                 StatVersion: statVersion,
332         }
333
334         for s.Scan() {
335                 ss := strings.Fields(string(s.Bytes()))
336                 if len(ss) == 0 {
337                         break
338                 }
339                 if len(ss) < 2 {
340                         return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
341                 }
342
343                 switch ss[0] {
344                 case fieldOpts:
345                         for _, opt := range strings.Split(ss[1], ",") {
346                                 split := strings.Split(opt, "=")
347                                 if len(split) == 2 && split[0] == "mountaddr" {
348                                         stats.MountAddress = split[1]
349                                 }
350                         }
351                 case fieldAge:
352                         // Age integer is in seconds
353                         d, err := time.ParseDuration(ss[1] + "s")
354                         if err != nil {
355                                 return nil, err
356                         }
357
358                         stats.Age = d
359                 case fieldBytes:
360                         bstats, err := parseNFSBytesStats(ss[1:])
361                         if err != nil {
362                                 return nil, err
363                         }
364
365                         stats.Bytes = *bstats
366                 case fieldEvents:
367                         estats, err := parseNFSEventsStats(ss[1:])
368                         if err != nil {
369                                 return nil, err
370                         }
371
372                         stats.Events = *estats
373                 case fieldTransport:
374                         if len(ss) < 3 {
375                                 return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
376                         }
377
378                         tstats, err := parseNFSTransportStats(ss[1:], statVersion)
379                         if err != nil {
380                                 return nil, err
381                         }
382
383                         stats.Transport = *tstats
384                 }
385
386                 // When encountering "per-operation statistics", we must break this
387                 // loop and parse them separately to ensure we can terminate parsing
388                 // before reaching another device entry; hence why this 'if' statement
389                 // is not just another switch case
390                 if ss[0] == fieldPerOpStats {
391                         break
392                 }
393         }
394
395         if err := s.Err(); err != nil {
396                 return nil, err
397         }
398
399         // NFS per-operation stats appear last before the next device entry
400         perOpStats, err := parseNFSOperationStats(s)
401         if err != nil {
402                 return nil, err
403         }
404
405         stats.Operations = perOpStats
406
407         return stats, nil
408 }
409
410 // parseNFSBytesStats parses a NFSBytesStats line using an input set of
411 // integer fields.
412 func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
413         if len(ss) != fieldBytesLen {
414                 return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)
415         }
416
417         ns := make([]uint64, 0, fieldBytesLen)
418         for _, s := range ss {
419                 n, err := strconv.ParseUint(s, 10, 64)
420                 if err != nil {
421                         return nil, err
422                 }
423
424                 ns = append(ns, n)
425         }
426
427         return &NFSBytesStats{
428                 Read:        ns[0],
429                 Write:       ns[1],
430                 DirectRead:  ns[2],
431                 DirectWrite: ns[3],
432                 ReadTotal:   ns[4],
433                 WriteTotal:  ns[5],
434                 ReadPages:   ns[6],
435                 WritePages:  ns[7],
436         }, nil
437 }
438
439 // parseNFSEventsStats parses a NFSEventsStats line using an input set of
440 // integer fields.
441 func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
442         if len(ss) != fieldEventsLen {
443                 return nil, fmt.Errorf("invalid NFS events stats: %v", ss)
444         }
445
446         ns := make([]uint64, 0, fieldEventsLen)
447         for _, s := range ss {
448                 n, err := strconv.ParseUint(s, 10, 64)
449                 if err != nil {
450                         return nil, err
451                 }
452
453                 ns = append(ns, n)
454         }
455
456         return &NFSEventsStats{
457                 InodeRevalidate:     ns[0],
458                 DnodeRevalidate:     ns[1],
459                 DataInvalidate:      ns[2],
460                 AttributeInvalidate: ns[3],
461                 VFSOpen:             ns[4],
462                 VFSLookup:           ns[5],
463                 VFSAccess:           ns[6],
464                 VFSUpdatePage:       ns[7],
465                 VFSReadPage:         ns[8],
466                 VFSReadPages:        ns[9],
467                 VFSWritePage:        ns[10],
468                 VFSWritePages:       ns[11],
469                 VFSGetdents:         ns[12],
470                 VFSSetattr:          ns[13],
471                 VFSFlush:            ns[14],
472                 VFSFsync:            ns[15],
473                 VFSLock:             ns[16],
474                 VFSFileRelease:      ns[17],
475                 CongestionWait:      ns[18],
476                 Truncation:          ns[19],
477                 WriteExtension:      ns[20],
478                 SillyRename:         ns[21],
479                 ShortRead:           ns[22],
480                 ShortWrite:          ns[23],
481                 JukeboxDelay:        ns[24],
482                 PNFSRead:            ns[25],
483                 PNFSWrite:           ns[26],
484         }, nil
485 }
486
487 // parseNFSOperationStats parses a slice of NFSOperationStats by scanning
488 // additional information about per-operation statistics until an empty
489 // line is reached.
490 func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
491         const (
492                 // Number of expected fields in each per-operation statistics set
493                 numFields = 9
494         )
495
496         var ops []NFSOperationStats
497
498         for s.Scan() {
499                 ss := strings.Fields(string(s.Bytes()))
500                 if len(ss) == 0 {
501                         // Must break when reading a blank line after per-operation stats to
502                         // enable top-level function to parse the next device entry
503                         break
504                 }
505
506                 if len(ss) != numFields {
507                         return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
508                 }
509
510                 // Skip string operation name for integers
511                 ns := make([]uint64, 0, numFields-1)
512                 for _, st := range ss[1:] {
513                         n, err := strconv.ParseUint(st, 10, 64)
514                         if err != nil {
515                                 return nil, err
516                         }
517
518                         ns = append(ns, n)
519                 }
520
521                 ops = append(ops, NFSOperationStats{
522                         Operation:                   strings.TrimSuffix(ss[0], ":"),
523                         Requests:                    ns[0],
524                         Transmissions:               ns[1],
525                         MajorTimeouts:               ns[2],
526                         BytesSent:                   ns[3],
527                         BytesReceived:               ns[4],
528                         CumulativeQueueTime:         time.Duration(ns[5]) * time.Millisecond,
529                         CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond,
530                         CumulativeTotalRequestTime:  time.Duration(ns[7]) * time.Millisecond,
531                 })
532         }
533
534         return ops, s.Err()
535 }
536
537 // parseNFSTransportStats parses a NFSTransportStats line using an input set of
538 // integer fields matched to a specific stats version.
539 func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
540         // Extract the protocol field. It is the only string value in the line
541         protocol := ss[0]
542         ss = ss[1:]
543
544         switch statVersion {
545         case statVersion10:
546                 var expectedLength int
547                 if protocol == "tcp" {
548                         expectedLength = fieldTransport10TCPLen
549                 } else if protocol == "udp" {
550                         expectedLength = fieldTransport10UDPLen
551                 } else {
552                         return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.0 statement: %v", protocol, ss)
553                 }
554                 if len(ss) != expectedLength {
555                         return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
556                 }
557         case statVersion11:
558                 var expectedLength int
559                 if protocol == "tcp" {
560                         expectedLength = fieldTransport11TCPLen
561                 } else if protocol == "udp" {
562                         expectedLength = fieldTransport11UDPLen
563                 } else {
564                         return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.1 statement: %v", protocol, ss)
565                 }
566                 if len(ss) != expectedLength {
567                         return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
568                 }
569         default:
570                 return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion)
571         }
572
573         // Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
574         // in a v1.0 response. Since the stat length is bigger for TCP stats, we use
575         // the TCP length here.
576         //
577         // Note: slice length must be set to length of v1.1 stats to avoid a panic when
578         // only v1.0 stats are present.
579         // See: https://github.com/prometheus/node_exporter/issues/571.
580         ns := make([]uint64, fieldTransport11TCPLen)
581         for i, s := range ss {
582                 n, err := strconv.ParseUint(s, 10, 64)
583                 if err != nil {
584                         return nil, err
585                 }
586
587                 ns[i] = n
588         }
589
590         // The fields differ depending on the transport protocol (TCP or UDP)
591         // From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt
592         //
593         // For the udp RPC transport there is no connection count, connect idle time,
594         // or idle time (fields #3, #4, and #5); all other fields are the same. So
595         // we set them to 0 here.
596         if protocol == "udp" {
597                 ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
598         }
599
600         return &NFSTransportStats{
601                 Protocol:                 protocol,
602                 Port:                     ns[0],
603                 Bind:                     ns[1],
604                 Connect:                  ns[2],
605                 ConnectIdleTime:          ns[3],
606                 IdleTime:                 time.Duration(ns[4]) * time.Second,
607                 Sends:                    ns[5],
608                 Receives:                 ns[6],
609                 BadTransactionIDs:        ns[7],
610                 CumulativeActiveRequests: ns[8],
611                 CumulativeBacklog:        ns[9],
612                 MaximumRPCSlotsUsed:      ns[10],
613                 CumulativeSendingQueue:   ns[11],
614                 CumulativePendingQueue:   ns[12],
615         }, nil
616 }