Skip to content

Commit

Permalink
Merge pull request #48 from adelolmo/feature/38-partition-access
Browse files Browse the repository at this point in the history
Use partitions read/write to calculate disk activity
  • Loading branch information
adelolmo authored Mar 28, 2021
2 parents d166222 + 98ab1dd commit 1e844d5
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
.run
*.iml
build
obj*
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Precompiled binaries for released versions are available in the
### Build from source

To build `hd-idle` from the source code yourself you need to have a working
Go environment with [version 1.12 or greater installed](http://golang.org/doc/install).
Go environment with [version 1.16 or greater installed](http://golang.org/doc/install).

You can directly use the `go` tool to download and install the `hd-idle`
binaries into your `GOPATH`:
Expand Down
65 changes: 40 additions & 25 deletions diskstats/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"regexp"
"strconv"
"strings"
"time"
)

/*
Expand Down Expand Up @@ -72,64 +71,72 @@ const (
writesCol = 9 // field 10 - sectors written
)

type DiskStats struct {
Name string
IdleTime time.Duration
CommandType string
Reads int
Writes int
SpinDownAt time.Time
SpinUpAt time.Time
LastIoAt time.Time
SpunDown bool
type ReadWriteStats struct {
Name string
Reads int
Writes int
}

var scsiDiskRegex *regexp.Regexp
var scsiDiskPartition *regexp.Regexp

func init() {
scsiDiskRegex = regexp.MustCompile("sd[a-z]$")
scsiDiskPartition = regexp.MustCompile("sd[a-z][0-9]+$")
}

func Snapshot() []DiskStats {
func Snapshot() []ReadWriteStats {
f, err := os.Open("/proc/diskstats")
if err != nil {
log.Fatal(err)
}
defer f.Close()

return ReadSnapshot(f)
return readSnapshot(f)
}

func ReadSnapshot(r io.Reader) []DiskStats {
var snapshot []DiskStats
func readSnapshot(r io.Reader) []ReadWriteStats {
diskStatsMap := make(map[string]ReadWriteStats)

scanner := bufio.NewScanner(r)
for scanner.Scan() {
diskStats, err := statsForDisk(scanner.Text())
if err == nil {
snapshot = append(snapshot, *diskStats)
if err != nil {
continue
}

if stat, ok := diskStatsMap[diskStats.Name]; ok {
stat.Writes += diskStats.Writes
stat.Reads += diskStats.Reads
diskStatsMap[diskStats.Name] = stat
continue
}

diskStatsMap[diskStats.Name] = *diskStats
}

if err := scanner.Err(); err != nil {
log.Fatal(err)
}

return snapshot
return toSlice(diskStatsMap)
}

func statsForDisk(rawStats string) (*DiskStats, error) {
func statsForDisk(rawStats string) (*ReadWriteStats, error) {
reader := strings.NewReader(rawStats)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
cols := strings.Fields(scanner.Text())

name := cols[deviceNameCol]
reads, _ := strconv.Atoi(cols[readsCol])
writes, _ := strconv.Atoi(cols[writesCol])
if !scsiDiskRegex.MatchString(name) {
return nil, errors.New("disk is a partition")

if !scsiDiskPartition.MatchString(name) {
continue
}
stats := &DiskStats{
Name: name,

diskName := name[:3] // remove the partition number
stats := &ReadWriteStats{
Name: diskName,
Reads: reads,
Writes: writes,
}
Expand All @@ -141,3 +148,11 @@ func statsForDisk(rawStats string) (*DiskStats, error) {
}
return nil, errors.New("cannot read disk stats")
}

func toSlice(rws map[string]ReadWriteStats) []ReadWriteStats {
var snapshot []ReadWriteStats
for _, r := range rws {
snapshot = append(snapshot, r)
}
return snapshot
}
24 changes: 18 additions & 6 deletions diskstats/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package diskstats

import (
"sort"
"strings"
"testing"
)
Expand All @@ -38,14 +39,25 @@ func TestTakeSnapshot(t *testing.T) {
8 32 sdc 52147 2738 6494584 913050 28092 1251 6370936 8938800 0 506360 9852970
8 33 sdc1 52087 2738 6493672 905390 28092 1251 6370936 8938800 0 498700 9892750
8 16 sdb 5650742 34516 727476416 92732820 1728864 35618 404215912 705303450 0 22944140 798112260
8 17 sdb1 5650643 34516 727475192 92673920 1728864 35618 404215912 705303450 0 22893010 798071230`
8 17 sdb1 5650643 34516 727475192 92673920 1728864 35618 404215912 705303450 0 22893010 798071230
8 16 sdd 982501 110903 37074938 9468348 60870 112203 15682640 17081448 0 2086868 26550788
8 17 sdd1 369 0 39960 1288 0 0 0 0 0 792 1288
8 18 sdd2 443 0 53496 1224 0 0 0 0 0 1204 1224
8 19 sdd3 52 0 2632 76 0 0 0 0 0 76 76
8 21 sdd5 76541 1090 22221672 979636 8845 2335 8316352 14986056 0 470364 15965544
8 22 sdd6 904573 109813 14736818 8485644 51818 109868 7366288 2094080 0 1642436 10580612
8 22 sdd11 904573 109813 1 8485644 51818 109868 1 2094080 0 1642436 10580612`

stats := ReadSnapshot(strings.NewReader(s))
stats := readSnapshot(strings.NewReader(s))
sort.Slice(stats, func(i, j int) bool {
return stats[i].Name < stats[j].Name
})

expected := []DiskStats{
{Name: "sda", Reads: 37537568, Writes: 10439592},
{Name: "sdc", Reads: 6494584, Writes: 6370936},
{Name: "sdb", Reads: 727476416, Writes: 404215912},
expected := []ReadWriteStats{
{Name: "sda", Reads: 37536344, Writes: 10439592},
{Name: "sdb", Reads: 727475192, Writes: 404215912},
{Name: "sdc", Reads: 6493672, Writes: 6370936},
{Name: "sdd", Reads: 37054579, Writes: 15682641},
}

if len(expected) != len(stats) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/adelolmo/hd-idle

go 1.12
go 1.16

require (
github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
Expand Down
29 changes: 23 additions & 6 deletions hdidle.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,19 @@ type Config struct {
SkewTime time.Duration
}

var previousSnapshots []diskstats.DiskStats
type DiskStats struct {
Name string
IdleTime time.Duration
CommandType string
Reads int
Writes int
SpinDownAt time.Time
SpinUpAt time.Time
LastIoAt time.Time
SpunDown bool
}

var previousSnapshots []DiskStats
var now = time.Now()
var lastNow = time.Now()

Expand All @@ -64,7 +76,12 @@ func ObserveDiskActivity(config *Config) {
now = time.Now()
resolveSymlinks(config)
for _, stats := range actualSnapshot {
updateState(stats, config)
d := &DiskStats{
Name: stats.Name,
Reads: stats.Reads,
Writes: stats.Writes,
}
updateState(*d, config)
}
lastNow = now
}
Expand All @@ -89,7 +106,7 @@ func resolveSymlinks(config *Config) {
}
}

func updateState(tmp diskstats.DiskStats, config *Config) {
func updateState(tmp DiskStats, config *Config) {
dsi := previousDiskStatsIndex(tmp.Name)
if dsi < 0 {
previousSnapshots = append(previousSnapshots, initDevice(tmp, config))
Expand Down Expand Up @@ -156,7 +173,7 @@ func previousDiskStatsIndex(diskName string) int {
return -1
}

func initDevice(stats diskstats.DiskStats, config *Config) diskstats.DiskStats {
func initDevice(stats DiskStats, config *Config) DiskStats {
idle := config.Defaults.Idle
command := config.Defaults.CommandType
deviceConf := deviceConfig(stats.Name, config)
Expand All @@ -165,7 +182,7 @@ func initDevice(stats diskstats.DiskStats, config *Config) diskstats.DiskStats {
command = deviceConf.CommandType
}

return diskstats.DiskStats{
return DiskStats{
Name: stats.Name,
LastIoAt: time.Now(),
SpinUpAt: time.Now(),
Expand Down Expand Up @@ -206,7 +223,7 @@ func spindownDisk(device, command string) error {
return nil
}

func logSpinup(ds diskstats.DiskStats, file string) {
func logSpinup(ds DiskStats, file string) {
now := time.Now()
text := fmt.Sprintf("date: %s, time: %s, disk: %s, running: %d, stopped: %d",
now.Format("2006-01-02"), now.Format("15:04:05"), ds.Name,
Expand Down
1 change: 1 addition & 0 deletions vendor/modules.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# github.com/benmcclelland/sgio v0.0.0-20180629175614-f710aebf64c1
## explicit
github.com/benmcclelland/sgio

0 comments on commit 1e844d5

Please sign in to comment.