191 lines
4.7 KiB
Go
191 lines
4.7 KiB
Go
|
// +build linux
|
||
|
|
||
|
package dmcache
|
||
|
|
||
|
import (
|
||
|
"os/exec"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"errors"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
)
|
||
|
|
||
|
const metricName = "dmcache"
|
||
|
|
||
|
type cacheStatus struct {
|
||
|
device string
|
||
|
length int
|
||
|
target string
|
||
|
metadataBlocksize int
|
||
|
metadataUsed int
|
||
|
metadataTotal int
|
||
|
cacheBlocksize int
|
||
|
cacheUsed int
|
||
|
cacheTotal int
|
||
|
readHits int
|
||
|
readMisses int
|
||
|
writeHits int
|
||
|
writeMisses int
|
||
|
demotions int
|
||
|
promotions int
|
||
|
dirty int
|
||
|
}
|
||
|
|
||
|
func (c *DMCache) Gather(acc telegraf.Accumulator) error {
|
||
|
outputLines, err := c.getCurrentStatus()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
totalStatus := cacheStatus{}
|
||
|
|
||
|
for _, s := range outputLines {
|
||
|
status, err := parseDMSetupStatus(s)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if c.PerDevice {
|
||
|
tags := map[string]string{"device": status.device}
|
||
|
acc.AddFields(metricName, toFields(status), tags)
|
||
|
}
|
||
|
aggregateStats(&totalStatus, status)
|
||
|
}
|
||
|
|
||
|
acc.AddFields(metricName, toFields(totalStatus), map[string]string{"device": "all"})
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func parseDMSetupStatus(line string) (cacheStatus, error) {
|
||
|
var err error
|
||
|
parseError := errors.New("Output from dmsetup could not be parsed")
|
||
|
status := cacheStatus{}
|
||
|
values := strings.Fields(line)
|
||
|
if len(values) < 15 {
|
||
|
return cacheStatus{}, parseError
|
||
|
}
|
||
|
|
||
|
status.device = strings.TrimRight(values[0], ":")
|
||
|
status.length, err = strconv.Atoi(values[2])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.target = values[3]
|
||
|
status.metadataBlocksize, err = strconv.Atoi(values[4])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
metadata := strings.Split(values[5], "/")
|
||
|
if len(metadata) != 2 {
|
||
|
return cacheStatus{}, parseError
|
||
|
}
|
||
|
status.metadataUsed, err = strconv.Atoi(metadata[0])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.metadataTotal, err = strconv.Atoi(metadata[1])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.cacheBlocksize, err = strconv.Atoi(values[6])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
cache := strings.Split(values[7], "/")
|
||
|
if len(cache) != 2 {
|
||
|
return cacheStatus{}, parseError
|
||
|
}
|
||
|
status.cacheUsed, err = strconv.Atoi(cache[0])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.cacheTotal, err = strconv.Atoi(cache[1])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.readHits, err = strconv.Atoi(values[8])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.readMisses, err = strconv.Atoi(values[9])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.writeHits, err = strconv.Atoi(values[10])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.writeMisses, err = strconv.Atoi(values[11])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.demotions, err = strconv.Atoi(values[12])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.promotions, err = strconv.Atoi(values[13])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
status.dirty, err = strconv.Atoi(values[14])
|
||
|
if err != nil {
|
||
|
return cacheStatus{}, err
|
||
|
}
|
||
|
|
||
|
return status, nil
|
||
|
}
|
||
|
|
||
|
func aggregateStats(totalStatus *cacheStatus, status cacheStatus) {
|
||
|
totalStatus.length += status.length
|
||
|
totalStatus.metadataBlocksize += status.metadataBlocksize
|
||
|
totalStatus.metadataUsed += status.metadataUsed
|
||
|
totalStatus.metadataTotal += status.metadataTotal
|
||
|
totalStatus.cacheBlocksize += status.cacheBlocksize
|
||
|
totalStatus.cacheUsed += status.cacheUsed
|
||
|
totalStatus.cacheTotal += status.cacheTotal
|
||
|
totalStatus.readHits += status.readHits
|
||
|
totalStatus.readMisses += status.readMisses
|
||
|
totalStatus.writeHits += status.writeHits
|
||
|
totalStatus.writeMisses += status.writeMisses
|
||
|
totalStatus.demotions += status.demotions
|
||
|
totalStatus.promotions += status.promotions
|
||
|
totalStatus.dirty += status.dirty
|
||
|
}
|
||
|
|
||
|
func toFields(status cacheStatus) map[string]interface{} {
|
||
|
fields := make(map[string]interface{})
|
||
|
fields["length"] = status.length
|
||
|
fields["metadata_blocksize"] = status.metadataBlocksize
|
||
|
fields["metadata_used"] = status.metadataUsed
|
||
|
fields["metadata_total"] = status.metadataTotal
|
||
|
fields["cache_blocksize"] = status.cacheBlocksize
|
||
|
fields["cache_used"] = status.cacheUsed
|
||
|
fields["cache_total"] = status.cacheTotal
|
||
|
fields["read_hits"] = status.readHits
|
||
|
fields["read_misses"] = status.readMisses
|
||
|
fields["write_hits"] = status.writeHits
|
||
|
fields["write_misses"] = status.writeMisses
|
||
|
fields["demotions"] = status.demotions
|
||
|
fields["promotions"] = status.promotions
|
||
|
fields["dirty"] = status.dirty
|
||
|
return fields
|
||
|
}
|
||
|
|
||
|
func dmSetupStatus() ([]string, error) {
|
||
|
out, err := exec.Command("/bin/sh", "-c", "sudo /sbin/dmsetup status --target cache").Output()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if string(out) == "No devices found\n" {
|
||
|
return []string{}, nil
|
||
|
}
|
||
|
|
||
|
outString := strings.TrimRight(string(out), "\n")
|
||
|
status := strings.Split(outString, "\n")
|
||
|
|
||
|
return status, nil
|
||
|
}
|