Add NFSd stats (#70)
* Add NFSd parser
* Move util functions from xfs to procfs package.
* Update stats
* Move parsing tools to util package.
* Move util package.
* nfsd: Fix naming, make it compile, fix bugs
* nfsd: add initial tests
* nfsd: Add minimal bounds check on V4Ops
* Add bounds check to the `th` line.
* Add missing license headers.
* Make no matching nfsd line `unknown`.
* Use subtests.
Ben Kochie authored 6 years ago
Tobias Schmidt committed 6 years ago
0 | rc 0 6 18622 | |
1 | fh 0 0 0 0 0 | |
2 | io 157286400 0 | |
3 | th 8 0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 | |
4 | ra 32 0 0 0 0 0 0 0 0 0 0 0 | |
5 | net 18628 0 18628 6 | |
6 | rpc 18628 0 0 0 0 | |
7 | proc2 18 2 69 0 0 4410 0 0 0 0 0 0 0 0 0 0 0 99 2 | |
8 | proc3 22 2 112 0 2719 111 0 0 0 0 0 0 0 0 0 0 0 27 216 0 2 1 0 | |
9 | proc4 2 2 10853 | |
10 | proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150 1272 0 0 0 1236 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
4 | 4 | "os" |
5 | 5 | "path" |
6 | 6 | |
7 | "github.com/prometheus/procfs/nfsd" | |
7 | 8 | "github.com/prometheus/procfs/xfs" |
8 | 9 | ) |
9 | 10 | |
43 | 44 | |
44 | 45 | return xfs.ParseStats(f) |
45 | 46 | } |
47 | ||
48 | // NFSdRPCStats retrieves NFS daemon RPC statistics. | |
49 | func (fs FS) NFSdRPCStats() (*nfsd.RPCStats, error) { | |
50 | f, err := os.Open(fs.Path("net/rpc/nfsd")) | |
51 | if err != nil { | |
52 | return nil, err | |
53 | } | |
54 | defer f.Close() | |
55 | ||
56 | return nfsd.ParseRPCStats(f) | |
57 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package util | |
14 | ||
15 | import "strconv" | |
16 | ||
17 | // ParseUint32s parses a slice of strings into a slice of uint32s. | |
18 | func ParseUint32s(ss []string) ([]uint32, error) { | |
19 | us := make([]uint32, 0, len(ss)) | |
20 | for _, s := range ss { | |
21 | u, err := strconv.ParseUint(s, 10, 32) | |
22 | if err != nil { | |
23 | return nil, err | |
24 | } | |
25 | ||
26 | us = append(us, uint32(u)) | |
27 | } | |
28 | ||
29 | return us, nil | |
30 | } | |
31 | ||
32 | // ParseUint64s parses a slice of strings into a slice of uint64s. | |
33 | func ParseUint64s(ss []string) ([]uint64, error) { | |
34 | us := make([]uint64, 0, len(ss)) | |
35 | for _, s := range ss { | |
36 | u, err := strconv.ParseUint(s, 10, 64) | |
37 | if err != nil { | |
38 | return nil, err | |
39 | } | |
40 | ||
41 | us = append(us, u) | |
42 | } | |
43 | ||
44 | return us, nil | |
45 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | // Package nfsd implements parsing of /proc/net/rpc/nfsd. | |
14 | // Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/ | |
15 | package nfsd | |
16 | ||
17 | // ReplyCache models the "rc" line. | |
18 | type ReplyCache struct { | |
19 | Hits uint64 | |
20 | Misses uint64 | |
21 | NoCache uint64 | |
22 | } | |
23 | ||
24 | // FileHandles models the "fh" line. | |
25 | type FileHandles struct { | |
26 | Stale uint64 | |
27 | TotalLookups uint64 | |
28 | AnonLookups uint64 | |
29 | DirNoCache uint64 | |
30 | NoDirNoCache uint64 | |
31 | } | |
32 | ||
33 | // InputOutput models the "io" line. | |
34 | type InputOutput struct { | |
35 | Read uint64 | |
36 | Write uint64 | |
37 | } | |
38 | ||
39 | // Threads models the "th" line. | |
40 | type Threads struct { | |
41 | Threads uint64 | |
42 | FullCnt uint64 | |
43 | } | |
44 | ||
45 | // ReadAheadCache models the "ra" line. | |
46 | type ReadAheadCache struct { | |
47 | CacheSize uint64 | |
48 | CacheHistogram []uint64 | |
49 | NotFound uint64 | |
50 | } | |
51 | ||
52 | // Network models the "net" line. | |
53 | type Network struct { | |
54 | NetCount uint64 | |
55 | UDPCount uint64 | |
56 | TCPCount uint64 | |
57 | TCPConnect uint64 | |
58 | } | |
59 | ||
60 | // RPC models the "rpc" line. | |
61 | type RPC struct { | |
62 | RPCCount uint64 | |
63 | BadCnt uint64 | |
64 | BadFmt uint64 | |
65 | BadAuth uint64 | |
66 | BadcInt uint64 | |
67 | } | |
68 | ||
69 | // V2Stats models the "proc2" line. | |
70 | type V2Stats struct { | |
71 | Null uint64 | |
72 | GetAttr uint64 | |
73 | SetAttr uint64 | |
74 | Root uint64 | |
75 | Lookup uint64 | |
76 | ReadLink uint64 | |
77 | Read uint64 | |
78 | WrCache uint64 | |
79 | Write uint64 | |
80 | Create uint64 | |
81 | Remove uint64 | |
82 | Rename uint64 | |
83 | Link uint64 | |
84 | SymLink uint64 | |
85 | MkDir uint64 | |
86 | RmDir uint64 | |
87 | ReadDir uint64 | |
88 | FsStat uint64 | |
89 | } | |
90 | ||
91 | // V3Stats models the "proc3" line. | |
92 | type V3Stats struct { | |
93 | Null uint64 | |
94 | GetAttr uint64 | |
95 | SetAttr uint64 | |
96 | Lookup uint64 | |
97 | Access uint64 | |
98 | ReadLink uint64 | |
99 | Read uint64 | |
100 | Write uint64 | |
101 | Create uint64 | |
102 | MkDir uint64 | |
103 | SymLink uint64 | |
104 | MkNod uint64 | |
105 | Remove uint64 | |
106 | RmDir uint64 | |
107 | Rename uint64 | |
108 | Link uint64 | |
109 | ReadDir uint64 | |
110 | ReadDirPlus uint64 | |
111 | FsStat uint64 | |
112 | FsInfo uint64 | |
113 | PathConf uint64 | |
114 | Commit uint64 | |
115 | } | |
116 | ||
117 | // V4Stats models the "proc4" line. | |
118 | type V4Stats struct { | |
119 | Null uint64 | |
120 | Compound uint64 | |
121 | } | |
122 | ||
123 | // V4Ops models the "proc4ops" line: NFSv4 operations | |
124 | // Variable list, see: | |
125 | // v4.0 https://tools.ietf.org/html/rfc3010 (38 operations) | |
126 | // v4.1 https://tools.ietf.org/html/rfc5661 (58 operations) | |
127 | // v4.2 https://tools.ietf.org/html/draft-ietf-nfsv4-minorversion2-41 (71 operations) | |
128 | type V4Ops struct { | |
129 | //Values uint64 // Variable depending on v4.x sub-version. TODO: Will this always at least include the fields in this struct? | |
130 | Op0Unused uint64 | |
131 | Op1Unused uint64 | |
132 | Op2Future uint64 | |
133 | Access uint64 | |
134 | Close uint64 | |
135 | Commit uint64 | |
136 | Create uint64 | |
137 | DelegPurge uint64 | |
138 | DelegReturn uint64 | |
139 | GetAttr uint64 | |
140 | GetFH uint64 | |
141 | Link uint64 | |
142 | Lock uint64 | |
143 | Lockt uint64 | |
144 | Locku uint64 | |
145 | Lookup uint64 | |
146 | LookupRoot uint64 | |
147 | Nverify uint64 | |
148 | Open uint64 | |
149 | OpenAttr uint64 | |
150 | OpenConfirm uint64 | |
151 | OpenDgrd uint64 | |
152 | PutFH uint64 | |
153 | PutPubFH uint64 | |
154 | PutRootFH uint64 | |
155 | Read uint64 | |
156 | ReadDir uint64 | |
157 | ReadLink uint64 | |
158 | Remove uint64 | |
159 | Rename uint64 | |
160 | Renew uint64 | |
161 | RestoreFH uint64 | |
162 | SaveFH uint64 | |
163 | SecInfo uint64 | |
164 | SetAttr uint64 | |
165 | Verify uint64 | |
166 | Write uint64 | |
167 | RelLockOwner uint64 | |
168 | } | |
169 | ||
170 | // RPCStats models all stats from /proc/net/rpc/nfsd. | |
171 | type RPCStats struct { | |
172 | ReplyCache ReplyCache | |
173 | FileHandles FileHandles | |
174 | InputOutput InputOutput | |
175 | Threads Threads | |
176 | ReadAheadCache ReadAheadCache | |
177 | Network Network | |
178 | RPC RPC | |
179 | V2Stats V2Stats | |
180 | V3Stats V3Stats | |
181 | V4Stats V4Stats | |
182 | V4Ops V4Ops | |
183 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package nfsd | |
14 | ||
15 | import ( | |
16 | "bufio" | |
17 | "fmt" | |
18 | "io" | |
19 | "strings" | |
20 | ||
21 | "github.com/prometheus/procfs/internal/util" | |
22 | ) | |
23 | ||
24 | // ParseRPCStats returns stats read from /proc/net/rpc/nfsd | |
25 | func ParseRPCStats(r io.Reader) (*RPCStats, error) { | |
26 | stats := &RPCStats{} | |
27 | ||
28 | scanner := bufio.NewScanner(r) | |
29 | for scanner.Scan() { | |
30 | line := scanner.Text() | |
31 | parts := strings.Fields(scanner.Text()) | |
32 | // require at least <key> <value> | |
33 | if len(parts) < 2 { | |
34 | return nil, fmt.Errorf("invalid NFSd metric line %q", line) | |
35 | } | |
36 | label := parts[0] | |
37 | ||
38 | var values []uint64 | |
39 | var err error | |
40 | if label == "th" { | |
41 | if len(parts) < 3 { | |
42 | return nil, fmt.Errorf("invalid NFSd th metric line %q", line) | |
43 | } | |
44 | values, err = util.ParseUint64s(parts[1:3]) | |
45 | } else { | |
46 | values, err = util.ParseUint64s(parts[1:]) | |
47 | } | |
48 | if err != nil { | |
49 | return nil, fmt.Errorf("error parsing NFSd metric line: %s", err) | |
50 | } | |
51 | ||
52 | switch metricLine := parts[0]; metricLine { | |
53 | case "rc": | |
54 | stats.ReplyCache, err = parseReplyCache(values) | |
55 | case "fh": | |
56 | stats.FileHandles, err = parseFileHandles(values) | |
57 | case "io": | |
58 | stats.InputOutput, err = parseInputOutput(values) | |
59 | case "th": | |
60 | stats.Threads, err = parseThreads(values) | |
61 | case "ra": | |
62 | stats.ReadAheadCache, err = parseReadAheadCache(values) | |
63 | case "net": | |
64 | stats.Network, err = parseNetwork(values) | |
65 | case "rpc": | |
66 | stats.RPC, err = parseRPC(values) | |
67 | case "proc2": | |
68 | stats.V2Stats, err = parseV2Stats(values) | |
69 | case "proc3": | |
70 | stats.V3Stats, err = parseV3Stats(values) | |
71 | case "proc4": | |
72 | stats.V4Stats, err = parseV4Stats(values) | |
73 | case "proc4ops": | |
74 | stats.V4Ops, err = parseV4Ops(values) | |
75 | default: | |
76 | return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine) | |
77 | } | |
78 | if err != nil { | |
79 | return nil, fmt.Errorf("errors parsing NFSd metric line: %s", err) | |
80 | } | |
81 | } | |
82 | ||
83 | if err := scanner.Err(); err != nil { | |
84 | return nil, fmt.Errorf("error scanning NFSd file: %s", err) | |
85 | } | |
86 | ||
87 | return stats, nil | |
88 | } | |
89 | ||
90 | func parseReplyCache(v []uint64) (ReplyCache, error) { | |
91 | if len(v) != 3 { | |
92 | return ReplyCache{}, fmt.Errorf("invalid ReplyCache line %q", v) | |
93 | } | |
94 | ||
95 | return ReplyCache{ | |
96 | Hits: v[0], | |
97 | Misses: v[1], | |
98 | NoCache: v[2], | |
99 | }, nil | |
100 | } | |
101 | ||
102 | func parseFileHandles(v []uint64) (FileHandles, error) { | |
103 | if len(v) != 5 { | |
104 | return FileHandles{}, fmt.Errorf("invalid FileHandles, line %q", v) | |
105 | } | |
106 | ||
107 | return FileHandles{ | |
108 | Stale: v[0], | |
109 | TotalLookups: v[1], | |
110 | AnonLookups: v[2], | |
111 | DirNoCache: v[3], | |
112 | NoDirNoCache: v[4], | |
113 | }, nil | |
114 | } | |
115 | ||
116 | func parseInputOutput(v []uint64) (InputOutput, error) { | |
117 | if len(v) != 2 { | |
118 | return InputOutput{}, fmt.Errorf("invalid InputOutput line %q", v) | |
119 | } | |
120 | ||
121 | return InputOutput{ | |
122 | Read: v[0], | |
123 | Write: v[1], | |
124 | }, nil | |
125 | } | |
126 | ||
127 | func parseThreads(v []uint64) (Threads, error) { | |
128 | if len(v) != 2 { | |
129 | return Threads{}, fmt.Errorf("invalid Threads line %q", v) | |
130 | } | |
131 | ||
132 | return Threads{ | |
133 | Threads: v[0], | |
134 | FullCnt: v[1], | |
135 | }, nil | |
136 | } | |
137 | ||
138 | func parseReadAheadCache(v []uint64) (ReadAheadCache, error) { | |
139 | if len(v) != 12 { | |
140 | return ReadAheadCache{}, fmt.Errorf("invalid ReadAheadCache line %q", v) | |
141 | } | |
142 | ||
143 | return ReadAheadCache{ | |
144 | CacheSize: v[0], | |
145 | CacheHistogram: v[1:11], | |
146 | NotFound: v[11], | |
147 | }, nil | |
148 | } | |
149 | ||
150 | func parseNetwork(v []uint64) (Network, error) { | |
151 | if len(v) != 4 { | |
152 | return Network{}, fmt.Errorf("invalid Network line %q", v) | |
153 | } | |
154 | ||
155 | return Network{ | |
156 | NetCount: v[0], | |
157 | UDPCount: v[1], | |
158 | TCPCount: v[2], | |
159 | TCPConnect: v[3], | |
160 | }, nil | |
161 | } | |
162 | ||
163 | func parseRPC(v []uint64) (RPC, error) { | |
164 | if len(v) != 5 { | |
165 | return RPC{}, fmt.Errorf("invalid RPC line %q", v) | |
166 | } | |
167 | ||
168 | return RPC{ | |
169 | RPCCount: v[0], | |
170 | BadCnt: v[1], | |
171 | BadFmt: v[2], | |
172 | BadAuth: v[3], | |
173 | BadcInt: v[4], | |
174 | }, nil | |
175 | } | |
176 | ||
177 | func parseV2Stats(v []uint64) (V2Stats, error) { | |
178 | values := int(v[0]) | |
179 | if len(v[1:]) != values || values != 18 { | |
180 | return V2Stats{}, fmt.Errorf("invalid V2Stats line %q", v) | |
181 | } | |
182 | ||
183 | return V2Stats{ | |
184 | Null: v[1], | |
185 | GetAttr: v[2], | |
186 | SetAttr: v[3], | |
187 | Root: v[4], | |
188 | Lookup: v[5], | |
189 | ReadLink: v[6], | |
190 | Read: v[7], | |
191 | WrCache: v[8], | |
192 | Write: v[9], | |
193 | Create: v[10], | |
194 | Remove: v[11], | |
195 | Rename: v[12], | |
196 | Link: v[13], | |
197 | SymLink: v[14], | |
198 | MkDir: v[15], | |
199 | RmDir: v[16], | |
200 | ReadDir: v[17], | |
201 | FsStat: v[18], | |
202 | }, nil | |
203 | } | |
204 | ||
205 | func parseV3Stats(v []uint64) (V3Stats, error) { | |
206 | values := int(v[0]) | |
207 | if len(v[1:]) != values || values != 22 { | |
208 | return V3Stats{}, fmt.Errorf("invalid V3Stats line %q", v) | |
209 | } | |
210 | ||
211 | return V3Stats{ | |
212 | Null: v[1], | |
213 | GetAttr: v[2], | |
214 | SetAttr: v[3], | |
215 | Lookup: v[4], | |
216 | Access: v[5], | |
217 | ReadLink: v[6], | |
218 | Read: v[7], | |
219 | Write: v[8], | |
220 | Create: v[9], | |
221 | MkDir: v[10], | |
222 | SymLink: v[11], | |
223 | MkNod: v[12], | |
224 | Remove: v[13], | |
225 | RmDir: v[14], | |
226 | Rename: v[15], | |
227 | Link: v[16], | |
228 | ReadDir: v[17], | |
229 | ReadDirPlus: v[18], | |
230 | FsStat: v[19], | |
231 | FsInfo: v[20], | |
232 | PathConf: v[21], | |
233 | Commit: v[22], | |
234 | }, nil | |
235 | } | |
236 | ||
237 | func parseV4Stats(v []uint64) (V4Stats, error) { | |
238 | values := int(v[0]) | |
239 | if len(v[1:]) != values || values != 2 { | |
240 | return V4Stats{}, fmt.Errorf("invalid V4Stats line %q", v) | |
241 | } | |
242 | ||
243 | return V4Stats{ | |
244 | Null: v[1], | |
245 | Compound: v[2], | |
246 | }, nil | |
247 | } | |
248 | ||
249 | func parseV4Ops(v []uint64) (V4Ops, error) { | |
250 | values := int(v[0]) | |
251 | if len(v[1:]) != values || values < 39 { | |
252 | return V4Ops{}, fmt.Errorf("invalid V4Ops line %q", v) | |
253 | } | |
254 | ||
255 | stats := V4Ops{ | |
256 | Op0Unused: v[1], | |
257 | Op1Unused: v[2], | |
258 | Op2Future: v[3], | |
259 | Access: v[4], | |
260 | Close: v[5], | |
261 | Commit: v[6], | |
262 | Create: v[7], | |
263 | DelegPurge: v[8], | |
264 | DelegReturn: v[9], | |
265 | GetAttr: v[10], | |
266 | GetFH: v[11], | |
267 | Link: v[12], | |
268 | Lock: v[13], | |
269 | Lockt: v[14], | |
270 | Locku: v[15], | |
271 | Lookup: v[16], | |
272 | LookupRoot: v[17], | |
273 | Nverify: v[18], | |
274 | Open: v[19], | |
275 | OpenAttr: v[20], | |
276 | OpenConfirm: v[21], | |
277 | OpenDgrd: v[22], | |
278 | PutFH: v[23], | |
279 | PutPubFH: v[24], | |
280 | PutRootFH: v[25], | |
281 | Read: v[26], | |
282 | ReadDir: v[27], | |
283 | ReadLink: v[28], | |
284 | Remove: v[29], | |
285 | Rename: v[30], | |
286 | Renew: v[31], | |
287 | RestoreFH: v[32], | |
288 | SaveFH: v[33], | |
289 | SecInfo: v[34], | |
290 | SetAttr: v[35], | |
291 | Verify: v[36], | |
292 | Write: v[37], | |
293 | RelLockOwner: v[38], | |
294 | } | |
295 | ||
296 | return stats, nil | |
297 | } |
0 | // Copyright 2018 The Prometheus Authors | |
1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
2 | // you may not use this file except in compliance with the License. | |
3 | // You may obtain a copy of the License at | |
4 | // | |
5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
6 | // | |
7 | // Unless required by applicable law or agreed to in writing, software | |
8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
10 | // See the License for the specific language governing permissions and | |
11 | // limitations under the License. | |
12 | ||
13 | package nfsd_test | |
14 | ||
15 | import ( | |
16 | "reflect" | |
17 | "strings" | |
18 | "testing" | |
19 | ||
20 | "github.com/prometheus/procfs/nfsd" | |
21 | ) | |
22 | ||
23 | func TestNewNFSdRPCStats(t *testing.T) { | |
24 | tests := []struct { | |
25 | name string | |
26 | content string | |
27 | stats *nfsd.RPCStats | |
28 | invalid bool | |
29 | }{ | |
30 | { | |
31 | name: "invalid file", | |
32 | content: "invalid", | |
33 | invalid: true, | |
34 | }, { | |
35 | name: "good file", | |
36 | content: `rc 0 6 18622 | |
37 | fh 0 0 0 0 0 | |
38 | io 157286400 0 | |
39 | th 8 0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 | |
40 | ra 32 0 0 0 0 0 0 0 0 0 0 0 | |
41 | net 18628 0 18628 6 | |
42 | rpc 18628 0 0 0 0 | |
43 | proc2 18 2 69 0 0 4410 0 0 0 0 0 0 0 0 0 0 0 99 2 | |
44 | proc3 22 2 112 0 2719 111 0 0 0 0 0 0 0 0 0 0 0 27 216 0 2 1 0 | |
45 | proc4 2 2 10853 | |
46 | proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150 1272 0 0 0 1236 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | |
47 | `, | |
48 | stats: &nfsd.RPCStats{ | |
49 | ReplyCache: nfsd.ReplyCache{ | |
50 | Hits: 0, | |
51 | Misses: 6, | |
52 | NoCache: 18622, | |
53 | }, | |
54 | FileHandles: nfsd.FileHandles{ | |
55 | Stale: 0, | |
56 | TotalLookups: 0, | |
57 | AnonLookups: 0, | |
58 | DirNoCache: 0, | |
59 | NoDirNoCache: 0, | |
60 | }, | |
61 | InputOutput: nfsd.InputOutput{ | |
62 | Read: 157286400, | |
63 | Write: 0, | |
64 | }, | |
65 | Threads: nfsd.Threads{ | |
66 | Threads: 8, | |
67 | FullCnt: 0, | |
68 | }, | |
69 | ReadAheadCache: nfsd.ReadAheadCache{ | |
70 | CacheSize: 32, | |
71 | CacheHistogram: []uint64{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | |
72 | NotFound: 0, | |
73 | }, | |
74 | Network: nfsd.Network{ | |
75 | NetCount: 18628, | |
76 | UDPCount: 0, | |
77 | TCPCount: 18628, | |
78 | TCPConnect: 6, | |
79 | }, | |
80 | RPC: nfsd.RPC{ | |
81 | RPCCount: 18628, | |
82 | BadCnt: 0, | |
83 | BadFmt: 0, | |
84 | BadAuth: 0, | |
85 | BadcInt: 0, | |
86 | }, | |
87 | V2Stats: nfsd.V2Stats{ | |
88 | Null: 2, | |
89 | GetAttr: 69, | |
90 | SetAttr: 0, | |
91 | Root: 0, | |
92 | Lookup: 4410, | |
93 | ReadLink: 0, | |
94 | Read: 0, | |
95 | WrCache: 0, | |
96 | Write: 0, | |
97 | Create: 0, | |
98 | Remove: 0, | |
99 | Rename: 0, | |
100 | Link: 0, | |
101 | SymLink: 0, | |
102 | MkDir: 0, | |
103 | RmDir: 0, | |
104 | ReadDir: 99, | |
105 | FsStat: 2, | |
106 | }, | |
107 | V3Stats: nfsd.V3Stats{ | |
108 | Null: 2, | |
109 | GetAttr: 112, | |
110 | SetAttr: 0, | |
111 | Lookup: 2719, | |
112 | Access: 111, | |
113 | ReadLink: 0, | |
114 | Read: 0, | |
115 | Write: 0, | |
116 | Create: 0, | |
117 | MkDir: 0, | |
118 | SymLink: 0, | |
119 | MkNod: 0, | |
120 | Remove: 0, | |
121 | RmDir: 0, | |
122 | Rename: 0, | |
123 | Link: 0, | |
124 | ReadDir: 27, | |
125 | ReadDirPlus: 216, | |
126 | FsStat: 0, | |
127 | FsInfo: 2, | |
128 | PathConf: 1, | |
129 | Commit: 0, | |
130 | }, | |
131 | V4Stats: nfsd.V4Stats{ | |
132 | Null: 2, | |
133 | Compound: 10853, | |
134 | }, | |
135 | V4Ops: nfsd.V4Ops{ | |
136 | Op0Unused: 0, | |
137 | Op1Unused: 0, | |
138 | Op2Future: 0, | |
139 | Access: 1098, | |
140 | Close: 2, | |
141 | Commit: 0, | |
142 | Create: 0, | |
143 | DelegPurge: 0, | |
144 | DelegReturn: 0, | |
145 | GetAttr: 8179, | |
146 | GetFH: 5896, | |
147 | Link: 0, | |
148 | Lock: 0, | |
149 | Lockt: 0, | |
150 | Locku: 0, | |
151 | Lookup: 5900, | |
152 | LookupRoot: 0, | |
153 | Nverify: 0, | |
154 | Open: 2, | |
155 | OpenAttr: 0, | |
156 | OpenConfirm: 2, | |
157 | OpenDgrd: 0, | |
158 | PutFH: 9609, | |
159 | PutPubFH: 0, | |
160 | PutRootFH: 2, | |
161 | Read: 150, | |
162 | ReadDir: 1272, | |
163 | ReadLink: 0, | |
164 | Remove: 0, | |
165 | Rename: 0, | |
166 | Renew: 1236, | |
167 | RestoreFH: 0, | |
168 | SaveFH: 0, | |
169 | SecInfo: 0, | |
170 | SetAttr: 0, | |
171 | Verify: 3, | |
172 | Write: 3, | |
173 | RelLockOwner: 0, | |
174 | }, | |
175 | }, | |
176 | }, | |
177 | } | |
178 | ||
179 | for _, tt := range tests { | |
180 | t.Run(tt.name, func(t *testing.T) { | |
181 | stats, err := nfsd.ParseRPCStats(strings.NewReader(tt.content)) | |
182 | ||
183 | if tt.invalid && err == nil { | |
184 | t.Fatal("expected an error, but none occurred") | |
185 | } | |
186 | if !tt.invalid && err != nil { | |
187 | t.Fatalf("unexpected error: %v", err) | |
188 | } | |
189 | ||
190 | if want, have := tt.stats, stats; !reflect.DeepEqual(want, have) { | |
191 | t.Fatalf("unexpected NFS stats:\nwant:\n%v\nhave:\n%v", want, have) | |
192 | } | |
193 | }) | |
194 | } | |
195 | } |
16 | 16 | "bufio" |
17 | 17 | "fmt" |
18 | 18 | "io" |
19 | "strconv" | |
20 | 19 | "strings" |
20 | ||
21 | "github.com/prometheus/procfs/internal/util" | |
21 | 22 | ) |
22 | 23 | |
23 | 24 | // ParseStats parses a Stats from an input io.Reader, using the format |
67 | 68 | |
68 | 69 | // Extended precision counters are uint64 values. |
69 | 70 | if label == fieldXpc { |
70 | us, err := parseUint64s(ss[1:]) | |
71 | us, err := util.ParseUint64s(ss[1:]) | |
71 | 72 | if err != nil { |
72 | 73 | return nil, err |
73 | 74 | } |
81 | 82 | } |
82 | 83 | |
83 | 84 | // All other counters are uint32 values. |
84 | us, err := parseUint32s(ss[1:]) | |
85 | us, err := util.ParseUint32s(ss[1:]) | |
85 | 86 | if err != nil { |
86 | 87 | return nil, err |
87 | 88 | } |
326 | 327 | ReadBytes: us[2], |
327 | 328 | }, nil |
328 | 329 | } |
329 | ||
330 | // parseUint32s parses a slice of strings into a slice of uint32s. | |
331 | func parseUint32s(ss []string) ([]uint32, error) { | |
332 | us := make([]uint32, 0, len(ss)) | |
333 | for _, s := range ss { | |
334 | u, err := strconv.ParseUint(s, 10, 32) | |
335 | if err != nil { | |
336 | return nil, err | |
337 | } | |
338 | ||
339 | us = append(us, uint32(u)) | |
340 | } | |
341 | ||
342 | return us, nil | |
343 | } | |
344 | ||
345 | // parseUint64s parses a slice of strings into a slice of uint64s. | |
346 | func parseUint64s(ss []string) ([]uint64, error) { | |
347 | us := make([]uint64, 0, len(ss)) | |
348 | for _, s := range ss { | |
349 | u, err := strconv.ParseUint(s, 10, 64) | |
350 | if err != nil { | |
351 | return nil, err | |
352 | } | |
353 | ||
354 | us = append(us, u) | |
355 | } | |
356 | ||
357 | return us, nil | |
358 | } |