1
2
3
4
5
6
7
8
9
10
11
12
13
14 package btrfs
15
16 import (
17 "bufio"
18 "fmt"
19 "os"
20 "path"
21 "path/filepath"
22 "strconv"
23 "strings"
24
25 "github.com/prometheus/procfs/internal/fs"
26 "github.com/prometheus/procfs/internal/util"
27 )
28
29
30
31
32 const SectorSize = 512
33
34
35
36 type FS struct {
37 sys *fs.FS
38 }
39
40
41
42 func NewDefaultFS() (FS, error) {
43 return NewFS(fs.DefaultSysMountPoint)
44 }
45
46
47
48 func NewFS(mountPoint string) (FS, error) {
49 if strings.TrimSpace(mountPoint) == "" {
50 mountPoint = fs.DefaultSysMountPoint
51 }
52 sys, err := fs.NewFS(mountPoint)
53 if err != nil {
54 return FS{}, err
55 }
56 return FS{&sys}, nil
57 }
58
59
60 func (fs FS) Stats() ([]*Stats, error) {
61 matches, err := filepath.Glob(fs.sys.Path("fs/btrfs/*-*"))
62 if err != nil {
63 return nil, err
64 }
65
66 stats := make([]*Stats, 0, len(matches))
67 for _, uuidPath := range matches {
68 s, err := GetStats(uuidPath)
69 if err != nil {
70 return nil, err
71 }
72
73
74 if s.UUID == "" {
75 s.UUID = filepath.Base(uuidPath)
76 }
77
78 stats = append(stats, s)
79 }
80
81 return stats, nil
82 }
83
84
85 func GetStats(uuidPath string) (*Stats, error) {
86 r := &reader{path: uuidPath}
87 s := r.readFilesystemStats()
88 if r.err != nil {
89 return nil, r.err
90 }
91
92 return s, nil
93 }
94
95 type reader struct {
96 path string
97 err error
98 devCount int
99 }
100
101
102
103 func (r *reader) readFile(n string) string {
104 b, err := util.SysReadFile(path.Join(r.path, n))
105 if err != nil && !os.IsNotExist(err) {
106 r.err = err
107 }
108 return strings.TrimSpace(string(b))
109 }
110
111
112 func (r *reader) readValue(n string) (v uint64) {
113
114 s := r.readFile(n)
115 if r.err != nil {
116 return
117 }
118
119
120 v, _ = strconv.ParseUint(s, 10, 64)
121 return
122 }
123
124
125 func (r *reader) listFiles(p string) []string {
126 files, err := os.ReadDir(path.Join(r.path, p))
127 if err != nil {
128 r.err = err
129 return nil
130 }
131
132 names := make([]string, len(files))
133 for i, f := range files {
134 names[i] = f.Name()
135 }
136 return names
137 }
138
139
140 func (r *reader) readAllocationStats(d string) (a *AllocationStats) {
141
142 sr := &reader{path: path.Join(r.path, d), devCount: r.devCount}
143
144
145 a = &AllocationStats{
146
147 MayUseBytes: sr.readValue("bytes_may_use"),
148 PinnedBytes: sr.readValue("bytes_pinned"),
149 ReadOnlyBytes: sr.readValue("bytes_readonly"),
150 ReservedBytes: sr.readValue("bytes_reserved"),
151 UsedBytes: sr.readValue("bytes_used"),
152 DiskUsedBytes: sr.readValue("disk_used"),
153 DiskTotalBytes: sr.readValue("disk_total"),
154 Flags: sr.readValue("flags"),
155 TotalBytes: sr.readValue("total_bytes"),
156 TotalPinnedBytes: sr.readValue("total_bytes_pinned"),
157 Layouts: sr.readLayouts(),
158 }
159
160
161 r.err = sr.err
162
163 return
164 }
165
166
167 func (r *reader) readLayouts() map[string]*LayoutUsage {
168 files, err := os.ReadDir(r.path)
169 if err != nil {
170 r.err = err
171 return nil
172 }
173
174 m := make(map[string]*LayoutUsage)
175 for _, f := range files {
176 if f.IsDir() {
177 m[f.Name()] = r.readLayout(f.Name())
178 }
179 }
180
181 return m
182 }
183
184
185 func (r *reader) readLayout(p string) (l *LayoutUsage) {
186 l = new(LayoutUsage)
187 l.TotalBytes = r.readValue(path.Join(p, "total_bytes"))
188 l.UsedBytes = r.readValue(path.Join(p, "used_bytes"))
189 l.Ratio = r.calcRatio(p)
190
191 return
192 }
193
194
195 func (r *reader) calcRatio(p string) float64 {
196 switch p {
197 case "single", "raid0":
198 return 1
199 case "dup", "raid1", "raid10":
200 return 2
201 case "raid5":
202 return float64(r.devCount) / (float64(r.devCount) - 1)
203 case "raid6":
204 return float64(r.devCount) / (float64(r.devCount) - 2)
205 default:
206 return 0
207 }
208 }
209
210
211 func (r *reader) readDeviceInfo(d string) map[string]*Device {
212 devs := r.listFiles(d)
213 info := make(map[string]*Device, len(devs))
214 for _, n := range devs {
215 info[n] = &Device{
216 Size: SectorSize * r.readValue("devices/"+n+"/size"),
217 }
218 }
219
220 return info
221 }
222
223
224 func (r *reader) readFilesystemStats() (s *Stats) {
225
226 devices := r.readDeviceInfo("devices")
227 r.devCount = len(devices)
228
229 s = &Stats{
230
231 Label: r.readFile("label"),
232 UUID: r.readFile("metadata_uuid"),
233 Features: r.listFiles("features"),
234 CloneAlignment: r.readValue("clone_alignment"),
235 NodeSize: r.readValue("nodesize"),
236 QuotaOverride: r.readValue("quota_override"),
237 SectorSize: r.readValue("sectorsize"),
238
239
240 Devices: devices,
241
242
243 Allocation: Allocation{
244 GlobalRsvReserved: r.readValue("allocation/global_rsv_reserved"),
245 GlobalRsvSize: r.readValue("allocation/global_rsv_size"),
246 Data: r.readAllocationStats("allocation/data"),
247 Metadata: r.readAllocationStats("allocation/metadata"),
248 System: r.readAllocationStats("allocation/system"),
249 },
250
251
252 CommitStats: r.readCommitStats("commit_stats"),
253 }
254 return
255 }
256
257
258 func (r *reader) readCommitStats(p string) CommitStats {
259 stats := CommitStats{}
260
261 f, err := os.Open(path.Join(r.path, p))
262 if err != nil {
263
264 if !os.IsNotExist(err) {
265 r.err = err
266 }
267 return stats
268 }
269 defer f.Close()
270
271 scanner := bufio.NewScanner(f)
272
273 for scanner.Scan() {
274 line := scanner.Text()
275 parts := strings.Fields(scanner.Text())
276
277 if len(parts) != 2 {
278 r.err = fmt.Errorf("invalid commit_stats line %q", line)
279 return stats
280 }
281
282 value, err := strconv.ParseUint(parts[1], 10, 64)
283 if err != nil {
284 r.err = fmt.Errorf("error parsing commit_stats line: %w", err)
285 return stats
286 }
287
288 switch metricName := parts[0]; metricName {
289 case "commits":
290 stats.Commits = value
291 case "last_commit_ms":
292 stats.LastCommitMs = value
293 case "max_commit_ms":
294 stats.MaxCommitMs = value
295 case "total_commit_ms":
296 stats.TotalCommitMs = value
297 default:
298 continue
299 }
300 }
301
302 if err := scanner.Err(); err != nil {
303 r.err = fmt.Errorf("error scanning commit_stats file: %w", err)
304 return stats
305 }
306
307 return stats
308 }
309
View as plain text