telegraf/plugins/inputs/diskio/diskio.go

225 lines
5.2 KiB
Go

package diskio
import (
"fmt"
"regexp"
"strings"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/plugins/inputs/system"
)
var (
varRegex = regexp.MustCompile(`\$(?:\w+|\{\w+\})`)
)
type DiskIO struct {
ps system.PS
Devices []string
DeviceTags []string
NameTemplates []string
SkipSerialNumber bool
Log telegraf.Logger
infoCache map[string]diskInfoCache
deviceFilter filter.Filter
initialized bool
}
func (_ *DiskIO) Description() string {
return "Read metrics about disk IO by device"
}
var diskIOsampleConfig = `
## By default, telegraf will gather stats for all devices including
## disk partitions.
## Setting devices will restrict the stats to the specified devices.
# devices = ["sda", "sdb", "vd*"]
## Uncomment the following line if you need disk serial numbers.
# skip_serial_number = false
#
## On systems which support it, device metadata can be added in the form of
## tags.
## Currently only Linux is supported via udev properties. You can view
## available properties for a device by running:
## 'udevadm info -q property -n /dev/sda'
## Note: Most, but not all, udev properties can be accessed this way. Properties
## that are currently inaccessible include DEVTYPE, DEVNAME, and DEVPATH.
# device_tags = ["ID_FS_TYPE", "ID_FS_USAGE"]
#
## Using the same metadata source as device_tags, you can also customize the
## name of the device via templates.
## The 'name_templates' parameter is a list of templates to try and apply to
## the device. The template may contain variables in the form of '$PROPERTY' or
## '${PROPERTY}'. The first template which does not contain any variables not
## present for the device is used as the device name tag.
## The typical use case is for LVM volumes, to get the VG/LV name instead of
## the near-meaningless DM-0 name.
# name_templates = ["$ID_FS_LABEL","$DM_VG_NAME/$DM_LV_NAME"]
`
func (_ *DiskIO) SampleConfig() string {
return diskIOsampleConfig
}
// hasMeta reports whether s contains any special glob characters.
func hasMeta(s string) bool {
return strings.IndexAny(s, "*?[") >= 0
}
func (s *DiskIO) init() error {
for _, device := range s.Devices {
if hasMeta(device) {
filter, err := filter.Compile(s.Devices)
if err != nil {
return fmt.Errorf("error compiling device pattern: %s", err.Error())
}
s.deviceFilter = filter
}
}
s.initialized = true
return nil
}
func (s *DiskIO) Gather(acc telegraf.Accumulator) error {
if !s.initialized {
err := s.init()
if err != nil {
return err
}
}
devices := []string{}
if s.deviceFilter == nil {
devices = s.Devices
}
diskio, err := s.ps.DiskIO(devices)
if err != nil {
return fmt.Errorf("error getting disk io info: %s", err.Error())
}
for _, io := range diskio {
match := false
if s.deviceFilter != nil && s.deviceFilter.Match(io.Name) {
match = true
}
tags := map[string]string{}
var devLinks []string
tags["name"], devLinks = s.diskName(io.Name)
if s.deviceFilter != nil && !match {
for _, devLink := range devLinks {
if s.deviceFilter.Match(devLink) {
match = true
break
}
}
if !match {
continue
}
}
for t, v := range s.diskTags(io.Name) {
tags[t] = v
}
if !s.SkipSerialNumber {
if len(io.SerialNumber) != 0 {
tags["serial"] = io.SerialNumber
} else {
tags["serial"] = "unknown"
}
}
fields := map[string]interface{}{
"reads": io.ReadCount,
"writes": io.WriteCount,
"read_bytes": io.ReadBytes,
"write_bytes": io.WriteBytes,
"read_time": io.ReadTime,
"write_time": io.WriteTime,
"io_time": io.IoTime,
"weighted_io_time": io.WeightedIO,
"iops_in_progress": io.IopsInProgress,
"merged_reads": io.MergedReadCount,
"merged_writes": io.MergedWriteCount,
}
acc.AddCounter("diskio", fields, tags)
}
return nil
}
func (s *DiskIO) diskName(devName string) (string, []string) {
di, err := s.diskInfo(devName)
devLinks := strings.Split(di["DEVLINKS"], " ")
for i, devLink := range devLinks {
devLinks[i] = strings.TrimPrefix(devLink, "/dev/")
}
if len(s.NameTemplates) == 0 {
return devName, devLinks
}
if err != nil {
s.Log.Warnf("Error gathering disk info: %s", err)
return devName, devLinks
}
for _, nt := range s.NameTemplates {
miss := false
name := varRegex.ReplaceAllStringFunc(nt, func(sub string) string {
sub = sub[1:] // strip leading '$'
if sub[0] == '{' {
sub = sub[1 : len(sub)-1] // strip leading & trailing '{' '}'
}
if v, ok := di[sub]; ok {
return v
}
miss = true
return ""
})
if !miss {
return name, devLinks
}
}
return devName, devLinks
}
func (s *DiskIO) diskTags(devName string) map[string]string {
if len(s.DeviceTags) == 0 {
return nil
}
di, err := s.diskInfo(devName)
if err != nil {
s.Log.Warnf("Error gathering disk info: %s", err)
return nil
}
tags := map[string]string{}
for _, dt := range s.DeviceTags {
if v, ok := di[dt]; ok {
tags[dt] = v
}
}
return tags
}
func init() {
ps := system.NewSystemPS()
inputs.Add("diskio", func() telegraf.Input {
return &DiskIO{ps: ps, SkipSerialNumber: true}
})
}