Skip to content

Commit

Permalink
Refactor diskstats (#2141)
Browse files Browse the repository at this point in the history
* Refactor diskstats_linux to use procfs.
* Add `node_disk_info` metric.

Signed-off-by: W. Andrew Denton <git@flying-snail.net>
Co-authored-by: W. Andrew Denton <git@flying-snail.net>
  • Loading branch information
ventifus and W. Andrew Denton committed Sep 28, 2021
1 parent 5de46c6 commit 0aec407
Show file tree
Hide file tree
Showing 3 changed files with 345 additions and 79 deletions.
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()
}

0 comments on commit 0aec407

Please sign in to comment.