cubefs

Форк
0
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

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 mount options of the NFS mount.
73
	Opts map[string]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
	CumulativeQueueMilliseconds uint64
185
	// Duration it took to get a reply back after the request was transmitted.
186
	CumulativeTotalResponseMilliseconds uint64
187
	// Duration from when a request was enqueued to when it was completely handled.
188
	CumulativeTotalRequestMilliseconds uint64
189
	// The count of operations that complete with tk_status < 0.  These statuses usually indicate error conditions.
190
	Errors uint64
191
}
192

193
// A NFSTransportStats contains statistics for the NFS mount RPC requests and
194
// responses.
195
type NFSTransportStats struct {
196
	// The transport protocol used for the NFS mount.
197
	Protocol string
198
	// The local port used for the NFS mount.
199
	Port uint64
200
	// Number of times the client has had to establish a connection from scratch
201
	// to the NFS server.
202
	Bind uint64
203
	// Number of times the client has made a TCP connection to the NFS server.
204
	Connect uint64
205
	// Duration (in jiffies, a kernel internal unit of time) the NFS mount has
206
	// spent waiting for connections to the server to be established.
207
	ConnectIdleTime uint64
208
	// Duration since the NFS mount last saw any RPC traffic.
209
	IdleTimeSeconds uint64
210
	// Number of RPC requests for this mount sent to the NFS server.
211
	Sends uint64
212
	// Number of RPC responses for this mount received from the NFS server.
213
	Receives uint64
214
	// Number of times the NFS server sent a response with a transaction ID
215
	// unknown to this client.
216
	BadTransactionIDs uint64
217
	// A running counter, incremented on each request as the current difference
218
	// ebetween sends and receives.
219
	CumulativeActiveRequests uint64
220
	// A running counter, incremented on each request by the current backlog
221
	// queue size.
222
	CumulativeBacklog uint64
223

224
	// Stats below only available with stat version 1.1.
225

226
	// Maximum number of simultaneously active RPC requests ever used.
227
	MaximumRPCSlotsUsed uint64
228
	// A running counter, incremented on each request as the current size of the
229
	// sending queue.
230
	CumulativeSendingQueue uint64
231
	// A running counter, incremented on each request as the current size of the
232
	// pending queue.
233
	CumulativePendingQueue uint64
234
}
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.
239
func parseMountStats(r io.Reader) ([]*Mount, error) {
240
	const (
241
		device            = "device"
242
		statVersionPrefix = "statvers="
243

244
		nfs3Type = "nfs"
245
		nfs4Type = "nfs4"
246
	)
247

248
	var mounts []*Mount
249

250
	s := bufio.NewScanner(r)
251
	for s.Scan() {
252
		// Only look for device entries in this function
253
		ss := strings.Fields(string(s.Bytes()))
254
		if len(ss) == 0 || ss[0] != device {
255
			continue
256
		}
257

258
		m, err := parseMount(ss)
259
		if err != nil {
260
			return nil, err
261
		}
262

263
		// Does this mount also possess statistics information?
264
		if len(ss) > deviceEntryLen {
265
			// Only NFSv3 and v4 are supported for parsing statistics
266
			if m.Type != nfs3Type && m.Type != nfs4Type {
267
				return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)
268
			}
269

270
			statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
271

272
			stats, err := parseMountStatsNFS(s, statVersion)
273
			if err != nil {
274
				return nil, err
275
			}
276

277
			m.Stats = stats
278
		}
279

280
		mounts = append(mounts, m)
281
	}
282

283
	return 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]
288
func parseMount(ss []string) (*Mount, error) {
289
	if len(ss) < deviceEntryLen {
290
		return nil, fmt.Errorf("invalid device entry: %v", ss)
291
	}
292

293
	// Check for specific words appearing at specific indices to ensure
294
	// the format is consistent with what we expect
295
	format := []struct {
296
		i int
297
		s string
298
	}{
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

306
	for _, f := range format {
307
		if ss[f.i] != f.s {
308
			return nil, fmt.Errorf("invalid device entry: %v", ss)
309
		}
310
	}
311

312
	return &Mount{
313
		Device: ss[1],
314
		Mount:  ss[4],
315
		Type:   ss[7],
316
	}, nil
317
}
318

319
// parseMountStatsNFS parses a MountStatsNFS by scanning additional information
320
// related to NFS statistics.
321
func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
322
	// Field indicators for parsing specific types of data
323
	const (
324
		fieldOpts       = "opts:"
325
		fieldAge        = "age:"
326
		fieldBytes      = "bytes:"
327
		fieldEvents     = "events:"
328
		fieldPerOpStats = "per-op"
329
		fieldTransport  = "xprt:"
330
	)
331

332
	stats := &MountStatsNFS{
333
		StatVersion: statVersion,
334
	}
335

336
	for s.Scan() {
337
		ss := strings.Fields(string(s.Bytes()))
338
		if len(ss) == 0 {
339
			break
340
		}
341

342
		switch ss[0] {
343
		case fieldOpts:
344
			if len(ss) < 2 {
345
				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
346
			}
347
			if stats.Opts == nil {
348
				stats.Opts = map[string]string{}
349
			}
350
			for _, opt := range strings.Split(ss[1], ",") {
351
				split := strings.Split(opt, "=")
352
				if len(split) == 2 {
353
					stats.Opts[split[0]] = split[1]
354
				} else {
355
					stats.Opts[opt] = ""
356
				}
357
			}
358
		case fieldAge:
359
			if len(ss) < 2 {
360
				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
361
			}
362
			// Age integer is in seconds
363
			d, err := time.ParseDuration(ss[1] + "s")
364
			if err != nil {
365
				return nil, err
366
			}
367

368
			stats.Age = d
369
		case fieldBytes:
370
			if len(ss) < 2 {
371
				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
372
			}
373
			bstats, err := parseNFSBytesStats(ss[1:])
374
			if err != nil {
375
				return nil, err
376
			}
377

378
			stats.Bytes = *bstats
379
		case fieldEvents:
380
			if len(ss) < 2 {
381
				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
382
			}
383
			estats, err := parseNFSEventsStats(ss[1:])
384
			if err != nil {
385
				return nil, err
386
			}
387

388
			stats.Events = *estats
389
		case fieldTransport:
390
			if len(ss) < 3 {
391
				return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
392
			}
393

394
			tstats, err := parseNFSTransportStats(ss[1:], statVersion)
395
			if err != nil {
396
				return nil, err
397
			}
398

399
			stats.Transport = *tstats
400
		}
401

402
		// When encountering "per-operation statistics", we must break this
403
		// loop and parse them separately to ensure we can terminate parsing
404
		// before reaching another device entry; hence why this 'if' statement
405
		// is not just another switch case
406
		if ss[0] == fieldPerOpStats {
407
			break
408
		}
409
	}
410

411
	if err := s.Err(); err != nil {
412
		return nil, err
413
	}
414

415
	// NFS per-operation stats appear last before the next device entry
416
	perOpStats, err := parseNFSOperationStats(s)
417
	if err != nil {
418
		return nil, err
419
	}
420

421
	stats.Operations = perOpStats
422

423
	return stats, nil
424
}
425

426
// parseNFSBytesStats parses a NFSBytesStats line using an input set of
427
// integer fields.
428
func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
429
	if len(ss) != fieldBytesLen {
430
		return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)
431
	}
432

433
	ns := make([]uint64, 0, fieldBytesLen)
434
	for _, s := range ss {
435
		n, err := strconv.ParseUint(s, 10, 64)
436
		if err != nil {
437
			return nil, err
438
		}
439

440
		ns = append(ns, n)
441
	}
442

443
	return &NFSBytesStats{
444
		Read:        ns[0],
445
		Write:       ns[1],
446
		DirectRead:  ns[2],
447
		DirectWrite: ns[3],
448
		ReadTotal:   ns[4],
449
		WriteTotal:  ns[5],
450
		ReadPages:   ns[6],
451
		WritePages:  ns[7],
452
	}, nil
453
}
454

455
// parseNFSEventsStats parses a NFSEventsStats line using an input set of
456
// integer fields.
457
func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
458
	if len(ss) != fieldEventsLen {
459
		return nil, fmt.Errorf("invalid NFS events stats: %v", ss)
460
	}
461

462
	ns := make([]uint64, 0, fieldEventsLen)
463
	for _, s := range ss {
464
		n, err := strconv.ParseUint(s, 10, 64)
465
		if err != nil {
466
			return nil, err
467
		}
468

469
		ns = append(ns, n)
470
	}
471

472
	return &NFSEventsStats{
473
		InodeRevalidate:     ns[0],
474
		DnodeRevalidate:     ns[1],
475
		DataInvalidate:      ns[2],
476
		AttributeInvalidate: ns[3],
477
		VFSOpen:             ns[4],
478
		VFSLookup:           ns[5],
479
		VFSAccess:           ns[6],
480
		VFSUpdatePage:       ns[7],
481
		VFSReadPage:         ns[8],
482
		VFSReadPages:        ns[9],
483
		VFSWritePage:        ns[10],
484
		VFSWritePages:       ns[11],
485
		VFSGetdents:         ns[12],
486
		VFSSetattr:          ns[13],
487
		VFSFlush:            ns[14],
488
		VFSFsync:            ns[15],
489
		VFSLock:             ns[16],
490
		VFSFileRelease:      ns[17],
491
		CongestionWait:      ns[18],
492
		Truncation:          ns[19],
493
		WriteExtension:      ns[20],
494
		SillyRename:         ns[21],
495
		ShortRead:           ns[22],
496
		ShortWrite:          ns[23],
497
		JukeboxDelay:        ns[24],
498
		PNFSRead:            ns[25],
499
		PNFSWrite:           ns[26],
500
	}, nil
501
}
502

503
// parseNFSOperationStats parses a slice of NFSOperationStats by scanning
504
// additional information about per-operation statistics until an empty
505
// line is reached.
506
func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
507
	const (
508
		// Minimum number of expected fields in each per-operation statistics set
509
		minFields = 9
510
	)
511

512
	var ops []NFSOperationStats
513

514
	for s.Scan() {
515
		ss := strings.Fields(string(s.Bytes()))
516
		if len(ss) == 0 {
517
			// Must break when reading a blank line after per-operation stats to
518
			// enable top-level function to parse the next device entry
519
			break
520
		}
521

522
		if len(ss) < minFields {
523
			return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
524
		}
525

526
		// Skip string operation name for integers
527
		ns := make([]uint64, 0, minFields-1)
528
		for _, st := range ss[1:] {
529
			n, err := strconv.ParseUint(st, 10, 64)
530
			if err != nil {
531
				return nil, err
532
			}
533

534
			ns = append(ns, n)
535
		}
536

537
		opStats := NFSOperationStats{
538
			Operation:                           strings.TrimSuffix(ss[0], ":"),
539
			Requests:                            ns[0],
540
			Transmissions:                       ns[1],
541
			MajorTimeouts:                       ns[2],
542
			BytesSent:                           ns[3],
543
			BytesReceived:                       ns[4],
544
			CumulativeQueueMilliseconds:         ns[5],
545
			CumulativeTotalResponseMilliseconds: ns[6],
546
			CumulativeTotalRequestMilliseconds:  ns[7],
547
		}
548

549
		if len(ns) > 8 {
550
			opStats.Errors = ns[8]
551
		}
552

553
		ops = append(ops, opStats)
554
	}
555

556
	return 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.
561
func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
562
	// Extract the protocol field. It is the only string value in the line
563
	protocol := ss[0]
564
	ss = ss[1:]
565

566
	switch statVersion {
567
	case statVersion10:
568
		var expectedLength int
569
		if protocol == "tcp" {
570
			expectedLength = fieldTransport10TCPLen
571
		} else if protocol == "udp" {
572
			expectedLength = fieldTransport10UDPLen
573
		} else {
574
			return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.0 statement: %v", protocol, ss)
575
		}
576
		if len(ss) != expectedLength {
577
			return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
578
		}
579
	case statVersion11:
580
		var expectedLength int
581
		if protocol == "tcp" {
582
			expectedLength = fieldTransport11TCPLen
583
		} else if protocol == "udp" {
584
			expectedLength = fieldTransport11UDPLen
585
		} else {
586
			return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.1 statement: %v", protocol, ss)
587
		}
588
		if len(ss) != expectedLength {
589
			return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
590
		}
591
	default:
592
		return 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 okay
596
	// in a v1.0 response. Since the stat length is bigger for TCP stats, we use
597
	// the TCP length here.
598
	//
599
	// Note: slice length must be set to length of v1.1 stats to avoid a panic when
600
	// only v1.0 stats are present.
601
	// See: https://github.com/prometheus/node_exporter/issues/571.
602
	ns := make([]uint64, fieldTransport11TCPLen)
603
	for i, s := range ss {
604
		n, err := strconv.ParseUint(s, 10, 64)
605
		if err != nil {
606
			return nil, err
607
		}
608

609
		ns[i] = n
610
	}
611

612
	// The fields differ depending on the transport protocol (TCP or UDP)
613
	// From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt
614
	//
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. So
617
	// we set them to 0 here.
618
	if protocol == "udp" {
619
		ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
620
	}
621

622
	return &NFSTransportStats{
623
		Protocol:                 protocol,
624
		Port:                     ns[0],
625
		Bind:                     ns[1],
626
		Connect:                  ns[2],
627
		ConnectIdleTime:          ns[3],
628
		IdleTimeSeconds:          ns[4],
629
		Sends:                    ns[5],
630
		Receives:                 ns[6],
631
		BadTransactionIDs:        ns[7],
632
		CumulativeActiveRequests: ns[8],
633
		CumulativeBacklog:        ns[9],
634
		MaximumRPCSlotsUsed:      ns[10],
635
		CumulativeSendingQueue:   ns[11],
636
		CumulativePendingQueue:   ns[12],
637
	}, nil
638
}
639

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.