forked from netdata/netdata
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
go.d add dmcache collector (netdata#17947)
- Loading branch information
Showing
14 changed files
with
925 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## All available configuration options, their descriptions and default values: | ||
## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/dmcache#readme | ||
|
||
jobs: | ||
- name: dmcache |
149 changes: 149 additions & 0 deletions
149
src/go/collectors/go.d.plugin/modules/dmcache/charts.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
package dmcache | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/netdata/netdata/go/go.d.plugin/agent/module" | ||
) | ||
|
||
const ( | ||
prioDeviceCacheSpaceUsage = module.Priority + iota | ||
prioDeviceMetaSpaceUsage | ||
prioDeviceReadEfficiency | ||
prioDeviceWriteEfficiency | ||
prioDeviceActivity | ||
prioDeviceDirty | ||
) | ||
|
||
var deviceChartsTmpl = module.Charts{ | ||
chartDeviceCacheSpaceUsageTmpl.Copy(), | ||
chartDeviceMetadataSpaceUsageTmpl.Copy(), | ||
|
||
chartDeviceReadEfficiencyTmpl.Copy(), | ||
chartDeviceWriteEfficiencyTmpl.Copy(), | ||
|
||
chartDeviceActivityTmpl.Copy(), | ||
|
||
chartDeviceDirtySizeTmpl.Copy(), | ||
} | ||
|
||
var ( | ||
chartDeviceCacheSpaceUsageTmpl = module.Chart{ | ||
ID: "dmcache_device_%s_cache_space_usage", | ||
Title: "DMCache space usage", | ||
Units: "bytes", | ||
Fam: "space usage", | ||
Ctx: "dmcache.device_cache_space_usage", | ||
Type: module.Stacked, | ||
Priority: prioDeviceCacheSpaceUsage, | ||
Dims: module.Dims{ | ||
{ID: "dmcache_device_%s_cache_free_bytes", Name: "free"}, | ||
{ID: "dmcache_device_%s_cache_used_bytes", Name: "used"}, | ||
}, | ||
} | ||
chartDeviceMetadataSpaceUsageTmpl = module.Chart{ | ||
ID: "dmcache_device_%s_metadata_space_usage", | ||
Title: "DMCache metadata space usage", | ||
Units: "bytes", | ||
Fam: "space usage", | ||
Ctx: "dmcache.device_metadata_space_usage", | ||
Type: module.Stacked, | ||
Priority: prioDeviceMetaSpaceUsage, | ||
Dims: module.Dims{ | ||
{ID: "dmcache_device_%s_metadata_free_bytes", Name: "free"}, | ||
{ID: "dmcache_device_%s_metadata_used_bytes", Name: "used"}, | ||
}, | ||
} | ||
) | ||
|
||
var ( | ||
chartDeviceReadEfficiencyTmpl = module.Chart{ | ||
ID: "dmcache_device_%s_read_efficiency", | ||
Title: "DMCache read efficiency", | ||
Units: "requests/s", | ||
Fam: "efficiency", | ||
Ctx: "dmcache.device_cache_read_efficiency", | ||
Type: module.Stacked, | ||
Priority: prioDeviceReadEfficiency, | ||
Dims: module.Dims{ | ||
{ID: "dmcache_device_%s_read_hits", Name: "hits", Algo: module.Incremental}, | ||
{ID: "dmcache_device_%s_read_misses", Name: "misses", Algo: module.Incremental}, | ||
}, | ||
} | ||
chartDeviceWriteEfficiencyTmpl = module.Chart{ | ||
ID: "dmcache_device_%s_write_efficiency", | ||
Title: "DMCache write efficiency", | ||
Units: "requests/s", | ||
Fam: "efficiency", | ||
Ctx: "dmcache.device_cache_write_efficiency", | ||
Type: module.Stacked, | ||
Priority: prioDeviceWriteEfficiency, | ||
Dims: module.Dims{ | ||
{ID: "dmcache_device_%s_write_hits", Name: "hits", Algo: module.Incremental}, | ||
{ID: "dmcache_device_%s_write_misses", Name: "misses", Algo: module.Incremental}, | ||
}, | ||
} | ||
) | ||
|
||
var chartDeviceActivityTmpl = module.Chart{ | ||
ID: "dmcache_device_%s_activity", | ||
Title: "DMCache activity", | ||
Units: "bytes/s", | ||
Fam: "activity", | ||
Ctx: "dmcache.device_cache_activity", | ||
Type: module.Area, | ||
Priority: prioDeviceActivity, | ||
Dims: module.Dims{ | ||
{ID: "dmcache_device_%s_promotions_bytes", Name: "promotions", Algo: module.Incremental}, | ||
{ID: "dmcache_device_%s_demotions_bytes", Name: "demotions", Mul: -1, Algo: module.Incremental}, | ||
}, | ||
} | ||
|
||
var chartDeviceDirtySizeTmpl = module.Chart{ | ||
ID: "dmcache_device_%s_dirty_size", | ||
Title: "DMCache dirty data size", | ||
Units: "bytes", | ||
Fam: "dirty size", | ||
Ctx: "dmcache.device_cache_dirty_size", | ||
Type: module.Area, | ||
Priority: prioDeviceDirty, | ||
Dims: module.Dims{ | ||
{ID: "dmcache_device_%s_dirty_bytes", Name: "dirty"}, | ||
}, | ||
} | ||
|
||
func (c *DmCache) addDeviceCharts(device string) { | ||
charts := deviceChartsTmpl.Copy() | ||
|
||
for _, chart := range *charts { | ||
chart.ID = fmt.Sprintf(chart.ID, cleanDeviceName(device)) | ||
chart.Labels = []module.Label{ | ||
{Key: "device", Value: device}, | ||
} | ||
for _, dim := range chart.Dims { | ||
dim.ID = fmt.Sprintf(dim.ID, device) | ||
} | ||
} | ||
|
||
if err := c.Charts().Add(*charts...); err != nil { | ||
c.Warning(err) | ||
} | ||
} | ||
|
||
func (c *DmCache) removeDeviceCharts(device string) { | ||
px := fmt.Sprintf("dmcache_device_%s_", cleanDeviceName(device)) | ||
|
||
for _, chart := range *c.Charts() { | ||
if strings.HasPrefix(chart.ID, px) { | ||
chart.MarkRemove() | ||
chart.MarkNotCreated() | ||
} | ||
} | ||
} | ||
|
||
func cleanDeviceName(device string) string { | ||
return strings.ReplaceAll(device, ".", "_") | ||
} |
173 changes: 173 additions & 0 deletions
173
src/go/collectors/go.d.plugin/modules/dmcache/collect.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
package dmcache | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type dmCacheDevice struct { | ||
name string | ||
metaBlockSizeSectors int64 | ||
metaUsedBlocks int64 | ||
metaTotalBlocks int64 | ||
cacheBlockSizeSectors int64 | ||
cacheUsedBlocks int64 | ||
cacheTotalBlocks int64 | ||
readHits int64 | ||
readMisses int64 | ||
writeHits int64 | ||
writeMisses int64 | ||
demotionsBlocks int64 | ||
promotionsBlocks int64 | ||
dirtyBlocks int64 | ||
} | ||
|
||
func (c *DmCache) collect() (map[string]int64, error) { | ||
bs, err := c.exec.cacheStatus() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
mx := make(map[string]int64) | ||
|
||
if err := c.collectCacheStatus(mx, bs); err != nil { | ||
return nil, err | ||
} | ||
|
||
return mx, nil | ||
} | ||
|
||
func (c *DmCache) collectCacheStatus(mx map[string]int64, data []byte) error { | ||
var devices []*dmCacheDevice | ||
|
||
sc := bufio.NewScanner(bytes.NewReader(data)) | ||
|
||
for sc.Scan() { | ||
line := strings.TrimSpace(sc.Text()) | ||
if line == "" { | ||
continue | ||
} | ||
|
||
dev, err := parseDmsetupStatusLine(line) | ||
if err != nil { | ||
return fmt.Errorf("malformed dmsetup status line: %v ('%s')", err, line) | ||
} | ||
|
||
devices = append(devices, dev) | ||
} | ||
|
||
seen := make(map[string]bool) | ||
|
||
for _, dev := range devices { | ||
seen[dev.name] = true | ||
|
||
if !c.devices[dev.name] { | ||
c.devices[dev.name] = true | ||
c.addDeviceCharts(dev.name) | ||
} | ||
|
||
px := fmt.Sprintf("dmcache_device_%s_", dev.name) | ||
|
||
const sectorSize = 512 | ||
metaMul := dev.metaBlockSizeSectors * sectorSize | ||
cacheMul := dev.cacheBlockSizeSectors * sectorSize | ||
|
||
mx[px+"metadata_free_bytes"] = (dev.metaTotalBlocks - dev.metaUsedBlocks) * metaMul | ||
mx[px+"metadata_used_bytes"] = dev.metaUsedBlocks * metaMul | ||
mx[px+"cache_free_bytes"] = (dev.cacheTotalBlocks - dev.cacheUsedBlocks) * cacheMul | ||
mx[px+"cache_used_bytes"] = dev.cacheUsedBlocks * cacheMul | ||
mx[px+"read_hits"] = dev.readHits | ||
mx[px+"read_misses"] = dev.readMisses | ||
mx[px+"write_hits"] = dev.writeHits | ||
mx[px+"write_misses"] = dev.writeMisses | ||
mx[px+"demotions_bytes"] = dev.demotionsBlocks * cacheMul | ||
mx[px+"promotions_bytes"] = dev.promotionsBlocks * cacheMul | ||
mx[px+"dirty_bytes"] = dev.dirtyBlocks * cacheMul | ||
} | ||
|
||
for dev := range c.devices { | ||
if !seen[dev] { | ||
delete(c.devices, dev) | ||
c.removeDeviceCharts(dev) | ||
} | ||
} | ||
|
||
if len(devices) == 0 { | ||
return errors.New("no dm-cache devices found") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func parseDmsetupStatusLine(line string) (*dmCacheDevice, error) { | ||
// https://www.kernel.org/doc/html/next/admin-guide/device-mapper/cache.html#status | ||
|
||
parts := strings.Fields(line) | ||
if len(parts) < 15 { | ||
return nil, fmt.Errorf("want at least 15 fields, got %d", len(parts)) | ||
} | ||
|
||
var dev dmCacheDevice | ||
var err error | ||
|
||
for i, s := range parts { | ||
switch i { | ||
case 0: | ||
dev.name = strings.TrimSuffix(parts[0], ":") | ||
case 4: | ||
dev.metaBlockSizeSectors, err = parseInt(s) | ||
case 5: | ||
dev.metaUsedBlocks, dev.metaTotalBlocks, err = parseUsedTotalBlocks(s) | ||
case 6: | ||
dev.cacheBlockSizeSectors, err = parseInt(s) | ||
case 7: | ||
dev.cacheUsedBlocks, dev.cacheTotalBlocks, err = parseUsedTotalBlocks(s) | ||
case 8: | ||
dev.readHits, err = parseInt(s) | ||
case 9: | ||
dev.readMisses, err = parseInt(s) | ||
case 10: | ||
dev.writeHits, err = parseInt(s) | ||
case 11: | ||
dev.writeMisses, err = parseInt(s) | ||
case 12: | ||
dev.demotionsBlocks, err = parseInt(s) | ||
case 13: | ||
dev.promotionsBlocks, err = parseInt(s) | ||
case 14: | ||
dev.dirtyBlocks, err = parseInt(s) | ||
} | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse %d field '%s': %v", i, s, err) | ||
} | ||
} | ||
|
||
return &dev, nil | ||
} | ||
|
||
func parseUsedTotalBlocks(info string) (int64, int64, error) { | ||
parts := strings.Split(info, "/") | ||
if len(parts) != 2 { | ||
return 0, 0, errors.New("expected used/total") | ||
} | ||
used, err := parseInt(parts[0]) | ||
if err != nil { | ||
return 0, 0, err | ||
} | ||
total, err := parseInt(parts[1]) | ||
if err != nil { | ||
return 0, 0, err | ||
} | ||
return used, total, nil | ||
} | ||
|
||
func parseInt(s string) (int64, error) { | ||
return strconv.ParseInt(s, 10, 64) | ||
} |
35 changes: 35 additions & 0 deletions
35
src/go/collectors/go.d.plugin/modules/dmcache/config_schema.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"jsonSchema": { | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"title": "DMCache collector configuration.", | ||
"type": "object", | ||
"properties": { | ||
"update_every": { | ||
"title": "Update every", | ||
"description": "Data collection interval, measured in seconds.", | ||
"type": "integer", | ||
"minimum": 1, | ||
"default": 10 | ||
}, | ||
"timeout": { | ||
"title": "Timeout", | ||
"description": "Timeout for executing the binary, specified in seconds.", | ||
"type": "number", | ||
"minimum": 0.5, | ||
"default": 2 | ||
} | ||
}, | ||
"additionalProperties": false, | ||
"patternProperties": { | ||
"^name$": {} | ||
} | ||
}, | ||
"uiSchema": { | ||
"uiOptions": { | ||
"fullPage": true | ||
}, | ||
"timeout": { | ||
"ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)." | ||
} | ||
} | ||
} |
Oops, something went wrong.