master
go 175 lines 3.97 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 //go:build linux
4
5 package dmcache
6
7 import (
8 "bufio"
9 "bytes"
10 "errors"
11 "fmt"
12 "strconv"
13 "strings"
14 )
15
16 type dmCacheDevice struct {
17 name string
18 metaBlockSizeSectors int64
19 metaUsedBlocks int64
20 metaTotalBlocks int64
21 cacheBlockSizeSectors int64
22 cacheUsedBlocks int64
23 cacheTotalBlocks int64
24 readHits int64
25 readMisses int64
26 writeHits int64
27 writeMisses int64
28 demotionsBlocks int64
29 promotionsBlocks int64
30 dirtyBlocks int64
31 }
32
33 func (c *Collector) collect() (map[string]int64, error) {
34 bs, err := c.exec.cacheStatus()
35 if err != nil {
36 return nil, err
37 }
38
39 mx := make(map[string]int64)
40
41 if err := c.collectCacheStatus(mx, bs); err != nil {
42 return nil, err
43 }
44
45 return mx, nil
46 }
47
48 func (c *Collector) collectCacheStatus(mx map[string]int64, data []byte) error {
49 var devices []*dmCacheDevice
50
51 sc := bufio.NewScanner(bytes.NewReader(data))
52
53 for sc.Scan() {
54 line := strings.TrimSpace(sc.Text())
55 if line == "" {
56 continue
57 }
58
59 dev, err := parseDmsetupStatusLine(line)
60 if err != nil {
61 return fmt.Errorf("malformed dmsetup status line: %v ('%s')", err, line)
62 }
63
64 devices = append(devices, dev)
65 }
66
67 seen := make(map[string]bool)
68
69 for _, dev := range devices {
70 seen[dev.name] = true
71
72 if !c.devices[dev.name] {
73 c.devices[dev.name] = true
74 c.addDeviceCharts(dev.name)
75 }
76
77 px := fmt.Sprintf("dmcache_device_%s_", dev.name)
78
79 const sectorSize = 512
80 metaMul := dev.metaBlockSizeSectors * sectorSize
81 cacheMul := dev.cacheBlockSizeSectors * sectorSize
82
83 mx[px+"metadata_free_bytes"] = (dev.metaTotalBlocks - dev.metaUsedBlocks) * metaMul
84 mx[px+"metadata_used_bytes"] = dev.metaUsedBlocks * metaMul
85 mx[px+"cache_free_bytes"] = (dev.cacheTotalBlocks - dev.cacheUsedBlocks) * cacheMul
86 mx[px+"cache_used_bytes"] = dev.cacheUsedBlocks * cacheMul
87 mx[px+"read_hits"] = dev.readHits
88 mx[px+"read_misses"] = dev.readMisses
89 mx[px+"write_hits"] = dev.writeHits
90 mx[px+"write_misses"] = dev.writeMisses
91 mx[px+"demotions_bytes"] = dev.demotionsBlocks * cacheMul
92 mx[px+"promotions_bytes"] = dev.promotionsBlocks * cacheMul
93 mx[px+"dirty_bytes"] = dev.dirtyBlocks * cacheMul
94 }
95
96 for dev := range c.devices {
97 if !seen[dev] {
98 delete(c.devices, dev)
99 c.removeDeviceCharts(dev)
100 }
101 }
102
103 if len(devices) == 0 {
104 return errors.New("no dm-cache devices found")
105 }
106
107 return nil
108 }
109
110 func parseDmsetupStatusLine(line string) (*dmCacheDevice, error) {
111 // https://www.kernel.org/doc/html/next/admin-guide/device-mapper/cache.html#status
112
113 parts := strings.Fields(line)
114 if len(parts) < 15 {
115 return nil, fmt.Errorf("want at least 15 fields, got %d", len(parts))
116 }
117
118 var dev dmCacheDevice
119 var err error
120
121 for i, s := range parts {
122 switch i {
123 case 0:
124 dev.name = strings.TrimSuffix(parts[0], ":")
125 case 4:
126 dev.metaBlockSizeSectors, err = parseInt(s)
127 case 5:
128 dev.metaUsedBlocks, dev.metaTotalBlocks, err = parseUsedTotalBlocks(s)
129 case 6:
130 dev.cacheBlockSizeSectors, err = parseInt(s)
131 case 7:
132 dev.cacheUsedBlocks, dev.cacheTotalBlocks, err = parseUsedTotalBlocks(s)
133 case 8:
134 dev.readHits, err = parseInt(s)
135 case 9:
136 dev.readMisses, err = parseInt(s)
137 case 10:
138 dev.writeHits, err = parseInt(s)
139 case 11:
140 dev.writeMisses, err = parseInt(s)
141 case 12:
142 dev.demotionsBlocks, err = parseInt(s)
143 case 13:
144 dev.promotionsBlocks, err = parseInt(s)
145 case 14:
146 dev.dirtyBlocks, err = parseInt(s)
147 }
148
149 if err != nil {
150 return nil, fmt.Errorf("failed to parse %d field '%s': %v", i, s, err)
151 }
152 }
153
154 return &dev, nil
155 }
156
157 func parseUsedTotalBlocks(info string) (int64, int64, error) {
158 parts := strings.Split(info, "/")
159 if len(parts) != 2 {
160 return 0, 0, errors.New("expected used/total")
161 }
162 used, err := parseInt(parts[0])
163 if err != nil {
164 return 0, 0, err
165 }
166 total, err := parseInt(parts[1])
167 if err != nil {
168 return 0, 0, err
169 }
170 return used, total, nil
171 }
172
173 func parseInt(s string) (int64, error) {
174 return strconv.ParseInt(s, 10, 64)
175 }