Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor diskstats #2141

Merged
merged 5 commits into from Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 53 additions & 58 deletions collector/diskstats_linux.go
Expand Up @@ -16,23 +16,18 @@
package collector

import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/procfs/blockdevice"
"gopkg.in/alecthomas/kingpin.v2"
)

const (
diskSectorSize = 512
diskstatsFilename = "diskstats"
secondsPerTick = 1.0 / 1000.0
)

var (
Expand All @@ -42,18 +37,16 @@ var (
type typedFactorDesc struct {
desc *prometheus.Desc
valueType prometheus.ValueType
factor float64
}

func (d *typedFactorDesc) mustNewConstMetric(value float64, labels ...string) prometheus.Metric {
if d.factor != 0 {
value *= d.factor
}
return prometheus.MustNewConstMetric(d.desc, d.valueType, value, labels...)
}

type diskstatsCollector struct {
ignoredDevicesPattern *regexp.Regexp
fs blockdevice.FS
infoDesc typedFactorDesc
descs []typedFactorDesc
logger log.Logger
}
Expand All @@ -66,9 +59,21 @@ func init() {
// Docs from https://www.kernel.org/doc/Documentation/iostats.txt
func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
var diskLabelNames = []string{"device"}
fs, err := blockdevice.NewFS(*procPath, *sysPath)
if err != nil {
return nil, fmt.Errorf("failed to open sysfs: %w", err)
}

return &diskstatsCollector{
ignoredDevicesPattern: regexp.MustCompile(*ignoredDevices),
fs: fs,
infoDesc: typedFactorDesc{
desc: prometheus.NewDesc(prometheus.BuildFQName(namespace, diskSubsystem, "info"),
"Info of /sys/block/<block_device>.",
[]string{"device", "major", "minor"},
nil,
), valueType: prometheus.GaugeValue,
},
descs: []typedFactorDesc{
{
desc: readsCompletedDesc, valueType: prometheus.CounterValue,
Expand All @@ -83,11 +88,9 @@ func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
},
{
desc: readBytesDesc, valueType: prometheus.CounterValue,
factor: diskSectorSize,
},
{
desc: readTimeSecondsDesc, valueType: prometheus.CounterValue,
factor: .001,
},
{
desc: writesCompletedDesc, valueType: prometheus.CounterValue,
Expand All @@ -102,11 +105,9 @@ func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
},
{
desc: writtenBytesDesc, valueType: prometheus.CounterValue,
factor: diskSectorSize,
},
{
desc: writeTimeSecondsDesc, valueType: prometheus.CounterValue,
factor: .001,
},
{
desc: prometheus.NewDesc(
Expand All @@ -118,7 +119,6 @@ func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
},
{
desc: ioTimeSecondsDesc, valueType: prometheus.CounterValue,
factor: .001,
},
{
desc: prometheus.NewDesc(
Expand All @@ -127,7 +127,6 @@ func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
diskLabelNames,
nil,
), valueType: prometheus.CounterValue,
factor: .001,
},
{
desc: prometheus.NewDesc(
Expand Down Expand Up @@ -160,7 +159,6 @@ func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
diskLabelNames,
nil,
), valueType: prometheus.CounterValue,
factor: .001,
},
{
desc: prometheus.NewDesc(
Expand All @@ -177,64 +175,61 @@ func NewDiskstatsCollector(logger log.Logger) (Collector, error) {
diskLabelNames,
nil,
), valueType: prometheus.CounterValue,
factor: .001,
},
},
logger: logger,
}, nil
}

func (c *diskstatsCollector) Update(ch chan<- prometheus.Metric) error {
diskStats, err := getDiskStats()
diskStats, err := c.fs.ProcDiskstats()
if err != nil {
return fmt.Errorf("couldn't get diskstats: %w", err)
}

for dev, stats := range diskStats {
for _, stats := range diskStats {
dev := stats.DeviceName
if c.ignoredDevicesPattern.MatchString(dev) {
level.Debug(c.logger).Log("msg", "Ignoring device", "device", dev)
level.Debug(c.logger).Log("msg", "Ignoring device", "device", dev, "pattern", c.ignoredDevicesPattern)
continue
}

for i, value := range stats {
// ignore unrecognized additional stats
if i >= len(c.descs) {
diskSectorSize := 512.0
blockQueue, err := c.fs.SysBlockDeviceQueueStats(dev)
if err != nil {
level.Debug(c.logger).Log("msg", "Error getting queue stats", "device", dev, "err", err)
} else {
diskSectorSize = float64(blockQueue.LogicalBlockSize)
}

ch <- c.infoDesc.mustNewConstMetric(1.0, dev, fmt.Sprint(stats.MajorNumber), fmt.Sprint(stats.MinorNumber))

statCount := stats.IoStatsCount - 3 // Total diskstats record count, less MajorNumber, MinorNumber and DeviceName

for i, val := range []float64{
float64(stats.ReadIOs),
float64(stats.ReadMerges),
float64(stats.ReadSectors) * diskSectorSize,
float64(stats.ReadTicks) * secondsPerTick,
float64(stats.WriteIOs),
float64(stats.WriteMerges),
float64(stats.WriteSectors) * diskSectorSize,
float64(stats.WriteTicks) * secondsPerTick,
float64(stats.IOsInProgress),
float64(stats.IOsTotalTicks) * secondsPerTick,
float64(stats.WeightedIOTicks) * secondsPerTick,
float64(stats.DiscardIOs),
float64(stats.DiscardMerges),
float64(stats.DiscardSectors),
float64(stats.DiscardTicks) * secondsPerTick,
float64(stats.FlushRequestsCompleted),
float64(stats.TimeSpentFlushing) * secondsPerTick,
} {
if i >= statCount {
break
}
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("invalid value %s in diskstats: %w", value, err)
}
ch <- c.descs[i].mustNewConstMetric(v, dev)
ch <- c.descs[i].mustNewConstMetric(val, dev)
}
}
return nil
}

func getDiskStats() (map[string][]string, error) {
file, err := os.Open(procFilePath(diskstatsFilename))
if err != nil {
return nil, err
}
defer file.Close()

return parseDiskStats(file)
}

func parseDiskStats(r io.Reader) (map[string][]string, error) {
var (
diskStats = map[string][]string{}
scanner = bufio.NewScanner(r)
)

for scanner.Scan() {
parts := strings.Fields(scanner.Text())
if len(parts) < 4 { // we strip major, minor and dev
return nil, fmt.Errorf("invalid line in %s: %s", procFilePath(diskstatsFilename), scanner.Text())
}
dev := parts[2]
diskStats[dev] = parts[3:]
}

return diskStats, scanner.Err()
}