1// Copyright 2017 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 xfs
15
16import (
17	"bufio"
18	"fmt"
19	"io"
20	"strings"
21
22	"github.com/prometheus/procfs/internal/util"
23)
24
25// ParseStats parses a Stats from an input io.Reader, using the format
26// found in /proc/fs/xfs/stat.
27func ParseStats(r io.Reader) (*Stats, error) {
28	const (
29		// Fields parsed into stats structures.
30		fieldExtentAlloc = "extent_alloc"
31		fieldAbt         = "abt"
32		fieldBlkMap      = "blk_map"
33		fieldBmbt        = "bmbt"
34		fieldDir         = "dir"
35		fieldTrans       = "trans"
36		fieldIg          = "ig"
37		fieldLog         = "log"
38		fieldRw          = "rw"
39		fieldAttr        = "attr"
40		fieldIcluster    = "icluster"
41		fieldVnodes      = "vnodes"
42		fieldBuf         = "buf"
43		fieldXpc         = "xpc"
44
45		// Unimplemented at this time due to lack of documentation.
46		fieldPushAil = "push_ail"
47		fieldXstrat  = "xstrat"
48		fieldAbtb2   = "abtb2"
49		fieldAbtc2   = "abtc2"
50		fieldBmbt2   = "bmbt2"
51		fieldIbt2    = "ibt2"
52		fieldFibt2   = "fibt2"
53		fieldQm      = "qm"
54		fieldDebug   = "debug"
55	)
56
57	var xfss Stats
58
59	s := bufio.NewScanner(r)
60	for s.Scan() {
61		// Expect at least a string label and a single integer value, ex:
62		//   - abt 0
63		//   - rw 1 2
64		ss := strings.Fields(string(s.Bytes()))
65		if len(ss) < 2 {
66			continue
67		}
68		label := ss[0]
69
70		// Extended precision counters are uint64 values.
71		if label == fieldXpc {
72			us, err := util.ParseUint64s(ss[1:])
73			if err != nil {
74				return nil, err
75			}
76
77			xfss.ExtendedPrecision, err = extendedPrecisionStats(us)
78			if err != nil {
79				return nil, err
80			}
81
82			continue
83		}
84
85		// All other counters are uint32 values.
86		us, err := util.ParseUint32s(ss[1:])
87		if err != nil {
88			return nil, err
89		}
90
91		switch label {
92		case fieldExtentAlloc:
93			xfss.ExtentAllocation, err = extentAllocationStats(us)
94		case fieldAbt:
95			xfss.AllocationBTree, err = btreeStats(us)
96		case fieldBlkMap:
97			xfss.BlockMapping, err = blockMappingStats(us)
98		case fieldBmbt:
99			xfss.BlockMapBTree, err = btreeStats(us)
100		case fieldDir:
101			xfss.DirectoryOperation, err = directoryOperationStats(us)
102		case fieldTrans:
103			xfss.Transaction, err = transactionStats(us)
104		case fieldIg:
105			xfss.InodeOperation, err = inodeOperationStats(us)
106		case fieldLog:
107			xfss.LogOperation, err = logOperationStats(us)
108		case fieldRw:
109			xfss.ReadWrite, err = readWriteStats(us)
110		case fieldAttr:
111			xfss.AttributeOperation, err = attributeOperationStats(us)
112		case fieldIcluster:
113			xfss.InodeClustering, err = inodeClusteringStats(us)
114		case fieldVnodes:
115			xfss.Vnode, err = vnodeStats(us)
116		case fieldBuf:
117			xfss.Buffer, err = bufferStats(us)
118		}
119		if err != nil {
120			return nil, err
121		}
122	}
123
124	return &xfss, s.Err()
125}
126
127// extentAllocationStats builds an ExtentAllocationStats from a slice of uint32s.
128func extentAllocationStats(us []uint32) (ExtentAllocationStats, error) {
129	if l := len(us); l != 4 {
130		return ExtentAllocationStats{}, fmt.Errorf("incorrect number of values for XFS extent allocation stats: %d", l)
131	}
132
133	return ExtentAllocationStats{
134		ExtentsAllocated: us[0],
135		BlocksAllocated:  us[1],
136		ExtentsFreed:     us[2],
137		BlocksFreed:      us[3],
138	}, nil
139}
140
141// btreeStats builds a BTreeStats from a slice of uint32s.
142func btreeStats(us []uint32) (BTreeStats, error) {
143	if l := len(us); l != 4 {
144		return BTreeStats{}, fmt.Errorf("incorrect number of values for XFS btree stats: %d", l)
145	}
146
147	return BTreeStats{
148		Lookups:         us[0],
149		Compares:        us[1],
150		RecordsInserted: us[2],
151		RecordsDeleted:  us[3],
152	}, nil
153}
154
155// BlockMappingStat builds a BlockMappingStats from a slice of uint32s.
156func blockMappingStats(us []uint32) (BlockMappingStats, error) {
157	if l := len(us); l != 7 {
158		return BlockMappingStats{}, fmt.Errorf("incorrect number of values for XFS block mapping stats: %d", l)
159	}
160
161	return BlockMappingStats{
162		Reads:                us[0],
163		Writes:               us[1],
164		Unmaps:               us[2],
165		ExtentListInsertions: us[3],
166		ExtentListDeletions:  us[4],
167		ExtentListLookups:    us[5],
168		ExtentListCompares:   us[6],
169	}, nil
170}
171
172// DirectoryOperationStats builds a DirectoryOperationStats from a slice of uint32s.
173func directoryOperationStats(us []uint32) (DirectoryOperationStats, error) {
174	if l := len(us); l != 4 {
175		return DirectoryOperationStats{}, fmt.Errorf("incorrect number of values for XFS directory operation stats: %d", l)
176	}
177
178	return DirectoryOperationStats{
179		Lookups:  us[0],
180		Creates:  us[1],
181		Removes:  us[2],
182		Getdents: us[3],
183	}, nil
184}
185
186// TransactionStats builds a TransactionStats from a slice of uint32s.
187func transactionStats(us []uint32) (TransactionStats, error) {
188	if l := len(us); l != 3 {
189		return TransactionStats{}, fmt.Errorf("incorrect number of values for XFS transaction stats: %d", l)
190	}
191
192	return TransactionStats{
193		Sync:  us[0],
194		Async: us[1],
195		Empty: us[2],
196	}, nil
197}
198
199// InodeOperationStats builds an InodeOperationStats from a slice of uint32s.
200func inodeOperationStats(us []uint32) (InodeOperationStats, error) {
201	if l := len(us); l != 7 {
202		return InodeOperationStats{}, fmt.Errorf("incorrect number of values for XFS inode operation stats: %d", l)
203	}
204
205	return InodeOperationStats{
206		Attempts:        us[0],
207		Found:           us[1],
208		Recycle:         us[2],
209		Missed:          us[3],
210		Duplicate:       us[4],
211		Reclaims:        us[5],
212		AttributeChange: us[6],
213	}, nil
214}
215
216// LogOperationStats builds a LogOperationStats from a slice of uint32s.
217func logOperationStats(us []uint32) (LogOperationStats, error) {
218	if l := len(us); l != 5 {
219		return LogOperationStats{}, fmt.Errorf("incorrect number of values for XFS log operation stats: %d", l)
220	}
221
222	return LogOperationStats{
223		Writes:            us[0],
224		Blocks:            us[1],
225		NoInternalBuffers: us[2],
226		Force:             us[3],
227		ForceSleep:        us[4],
228	}, nil
229}
230
231// ReadWriteStats builds a ReadWriteStats from a slice of uint32s.
232func readWriteStats(us []uint32) (ReadWriteStats, error) {
233	if l := len(us); l != 2 {
234		return ReadWriteStats{}, fmt.Errorf("incorrect number of values for XFS read write stats: %d", l)
235	}
236
237	return ReadWriteStats{
238		Read:  us[0],
239		Write: us[1],
240	}, nil
241}
242
243// AttributeOperationStats builds an AttributeOperationStats from a slice of uint32s.
244func attributeOperationStats(us []uint32) (AttributeOperationStats, error) {
245	if l := len(us); l != 4 {
246		return AttributeOperationStats{}, fmt.Errorf("incorrect number of values for XFS attribute operation stats: %d", l)
247	}
248
249	return AttributeOperationStats{
250		Get:    us[0],
251		Set:    us[1],
252		Remove: us[2],
253		List:   us[3],
254	}, nil
255}
256
257// InodeClusteringStats builds an InodeClusteringStats from a slice of uint32s.
258func inodeClusteringStats(us []uint32) (InodeClusteringStats, error) {
259	if l := len(us); l != 3 {
260		return InodeClusteringStats{}, fmt.Errorf("incorrect number of values for XFS inode clustering stats: %d", l)
261	}
262
263	return InodeClusteringStats{
264		Iflush:     us[0],
265		Flush:      us[1],
266		FlushInode: us[2],
267	}, nil
268}
269
270// VnodeStats builds a VnodeStats from a slice of uint32s.
271func vnodeStats(us []uint32) (VnodeStats, error) {
272	// The attribute "Free" appears to not be available on older XFS
273	// stats versions.  Therefore, 7 or 8 elements may appear in
274	// this slice.
275	l := len(us)
276	if l != 7 && l != 8 {
277		return VnodeStats{}, fmt.Errorf("incorrect number of values for XFS vnode stats: %d", l)
278	}
279
280	s := VnodeStats{
281		Active:   us[0],
282		Allocate: us[1],
283		Get:      us[2],
284		Hold:     us[3],
285		Release:  us[4],
286		Reclaim:  us[5],
287		Remove:   us[6],
288	}
289
290	// Skip adding free, unless it is present. The zero value will
291	// be used in place of an actual count.
292	if l == 7 {
293		return s, nil
294	}
295
296	s.Free = us[7]
297	return s, nil
298}
299
300// BufferStats builds a BufferStats from a slice of uint32s.
301func bufferStats(us []uint32) (BufferStats, error) {
302	if l := len(us); l != 9 {
303		return BufferStats{}, fmt.Errorf("incorrect number of values for XFS buffer stats: %d", l)
304	}
305
306	return BufferStats{
307		Get:             us[0],
308		Create:          us[1],
309		GetLocked:       us[2],
310		GetLockedWaited: us[3],
311		BusyLocked:      us[4],
312		MissLocked:      us[5],
313		PageRetries:     us[6],
314		PageFound:       us[7],
315		GetRead:         us[8],
316	}, nil
317}
318
319// ExtendedPrecisionStats builds an ExtendedPrecisionStats from a slice of uint32s.
320func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) {
321	if l := len(us); l != 3 {
322		return ExtendedPrecisionStats{}, fmt.Errorf("incorrect number of values for XFS extended precision stats: %d", l)
323	}
324
325	return ExtendedPrecisionStats{
326		FlushBytes: us[0],
327		WriteBytes: us[1],
328		ReadBytes:  us[2],
329	}, nil
330}
331