-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdaemon.go
150 lines (129 loc) · 3.35 KB
/
daemon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package main
import (
"bufio"
"errors"
"log"
"os"
"strconv"
"strings"
"time"
)
type daemon struct {
c *context
}
// Starts daemon and its periodical checks
func (d *daemon) start() {
if err := d.c.initDevices(); err != nil {
log.Fatal(err)
}
log.Print("quietdisk started")
sleep := 60
if sleep > d.c.idlePeriod {
sleep = d.c.idlePeriod
}
for {
if d.c.verbose {
log.Print("updating devices")
}
d.updateDevices()
time.Sleep(time.Duration(sleep) * time.Second)
}
}
// Performs updates on each device, if is available. Devices not listed in /proc/diskstats or partitions are skipped.
func (d *daemon) updateDevices() {
if err := d.refreshDevices(); err != nil {
log.Print(err)
}
b, err := os.ReadFile(pathDiskstats)
if err != nil {
log.Print(err)
}
diskstats := string(b)
scanner := bufio.NewScanner(strings.NewReader(diskstats))
for scanner.Scan() {
data := strings.Fields(scanner.Text())
if len(data) < 8 {
log.Printf("incorrect number of fields from %s", pathDiskstats)
continue
}
major, _ := strconv.Atoi(data[0])
minor, _ := strconv.Atoi(data[1])
name := data[2]
rIops, _ := strconv.ParseUint(data[3], 10, 64)
wIops, _ := strconv.ParseUint(data[7], 10, 64)
dev := d.getDevice(major, minor)
if dev == nil {
continue
}
dev.name = name
now := time.Now().Unix()
// Check for changes on read and write IOPS
if rIops >= dev.rIops && rIops-dev.rIops >= uint64(d.c.threshold) || wIops >= dev.wIops && wIops-dev.wIops >= uint64(d.c.threshold) {
dev.rIops, dev.wIops, dev.lastChange, dev.isSleeping = rIops, wIops, now, false
if d.c.verbose {
log.Printf("rIops or wIops has changed on '%s'", dev.device)
}
continue
}
// Check, if drive is really sleeping
if dev.isSleeping && dev.lastStandBy+int64(d.c.idlePeriod) <= now {
if isSleeping, err := dev.isDriveSleeping(); err != nil {
log.Print(err)
} else if !isSleeping {
log.Printf("'%s' is awake, but should be asleep ", dev.device)
dev.isSleeping = false
}
}
// If is not sleeping and should have, put device to sleep
if !dev.isSleeping && dev.lastChange+int64(d.c.idlePeriod) <= now {
if dev.lastStandBy == 0 || dev.lastStandBy+int64(d.c.gracePeriod) <= now {
dev.lastStandBy, dev.isSleeping = now, true
log.Printf("going to put '%s' to sleep ", dev.device)
if err := dev.putDriveToSleep(); err != nil {
log.Print(err)
}
} else {
if d.c.verbose {
log.Printf("it is too soon to put '%s' to sleep", dev.device)
}
}
}
}
}
// Refreshes map of devices to be used.
func (d *daemon) refreshDevices() error {
if d.c.allDevices {
devices := d.c.listAllDevices()
for _, id := range devices {
if _, exists := d.c.devices[id]; !exists {
var dev *device
var err error
dev, err = dev.initDevice(id, d.c.hddOnly)
if err == nil && dev != nil {
d.c.devices[id] = dev
}
}
}
}
if len(d.c.devices) == 0 {
return errors.New("no device is available")
}
for _, dev := range d.c.devices {
err := dev.updateMajorMinor()
if err != nil {
if d.c.allDevices {
delete(d.c.devices, dev.device)
}
}
}
return nil
}
// Gets device from map by major and minor identificators.
func (d *daemon) getDevice(major, minor int) *device {
for _, dev := range d.c.devices {
if dev.exists && dev.major == major && dev.minor == minor {
return dev
}
}
return nil
}