From 262018bec29c1d6b56f56a53628bc765a58e3003 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 18 May 2023 20:59:49 +0100 Subject: [PATCH] allow more than one method to check disk power status --- .github/workflows/build.yml | 6 +- .github/workflows/release.yml | 10 +-- cfg/config.go | 20 +++--- config.yaml | 30 +++++---- go.mod | 16 ++--- go.sum | 38 +++++------ hardware-events.service | 1 + lib/disk.go | 67 ++++++++++++------- lib/disk_status.go | 63 +++++++++++++---- lib/disk_status_test.go | 33 +++++++++ lib/disk_test.go | 2 +- .../dev/disk/by-id/ata-ST2000DM001-first | 0 .../dev/disk/by-id/ata-ST2000DM001-second | 0 .../dev/sda | 0 .../dev/sdb | 0 .../block/nvme0n1/device/hwmon1/temp1_input | 1 + .../sys/block/nvme0n1/device/state | 1 + .../block/sda/device/hwmon/hwmon4/temp1_input | 0 .../block/sdb/device/hwmon/hwmon5/temp1_input | 0 lib/global.go | 17 +++-- lib/sensor.go | 15 ++--- lib/simulation/disk_status.go | 4 +- main.go | 2 +- 23 files changed, 209 insertions(+), 117 deletions(-) create mode 100644 lib/disk_status_test.go rename lib/{drivetemp_test_files => fs_test_files}/dev/disk/by-id/ata-ST2000DM001-first (100%) rename lib/{drivetemp_test_files => fs_test_files}/dev/disk/by-id/ata-ST2000DM001-second (100%) rename lib/{drivetemp_test_files => fs_test_files}/dev/sda (100%) rename lib/{drivetemp_test_files => fs_test_files}/dev/sdb (100%) create mode 100644 lib/fs_test_files/sys/block/nvme0n1/device/hwmon1/temp1_input create mode 100644 lib/fs_test_files/sys/block/nvme0n1/device/state rename lib/{drivetemp_test_files => fs_test_files}/sys/block/sda/device/hwmon/hwmon4/temp1_input (100%) rename lib/{drivetemp_test_files => fs_test_files}/sys/block/sdb/device/hwmon/hwmon5/temp1_input (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4af9c32..5dca6f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,18 +13,18 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go_version: ['1.18'] + go_version: ['1.19', '1.20'] os: [ubuntu-latest] steps: - name: Set up Go ${{ matrix.go_version }} - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go_version }} - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Get dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea0255b..b795719 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,20 +12,20 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v3 with: - go-version: 1.18.x + go-version: 1.20.x - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v1 + uses: goreleaser/goreleaser-action@v4 with: version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/cfg/config.go b/cfg/config.go index ba66cda..7987bc3 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -10,18 +10,19 @@ import ( // Config from the file type Config struct { - Simulation bool `yaml:"simulation"` - DiskPowerStatus DiskPowerStatus `yaml:"disk_power_status"` - Sensors map[string]Task `yaml:"sensors"` - DiskPools map[string][]string `yaml:"disk_pools"` - Disks map[string]Disk `yaml:"disks"` - Templates map[string]Template `yaml:"templates"` - Tasks map[string]Task `yaml:"tasks"` - Schedule map[string]Schedule `yaml:"schedule"` - FanControl FanControl `yaml:"fan_control"` + Simulation bool `yaml:"simulation"` + DiskPowerStatus map[string]DiskPowerStatus `yaml:"disk_power_status"` + Sensors map[string]Task `yaml:"sensors"` + DiskPools map[string][]string `yaml:"disk_pools"` + Disks map[string]Disk `yaml:"disks"` + Templates map[string]Template `yaml:"templates"` + Tasks map[string]Task `yaml:"tasks"` + Schedule map[string]Schedule `yaml:"schedule"` + FanControl FanControl `yaml:"fan_control"` } type DiskPowerStatus struct { + File string `yaml:"file"` CheckCommand string `yaml:"check_command"` Active string `yaml:"active"` Standby string `yaml:"standby"` @@ -37,6 +38,7 @@ type Disk struct { MonitorTemperature string `yaml:"monitor_temperature"` LastActive string `yaml:"last_active"` StandbyAfter string `yaml:"standby_after"` + PowerStatus string `yaml:"power_status"` } type Template struct { diff --git a/config.yaml b/config.yaml index f2f1ddd..fc99dc2 100644 --- a/config.yaml +++ b/config.yaml @@ -16,9 +16,12 @@ sensors: pch: file: "/sys/class/thermal/thermal_zone1/temp" divider: 1000 - drivetemp: + satatemp: file: "/sys/block/${DEVICE_NAME}/device/hwmon/hwmon*/temp1_input" divider: 1000 + nvmetemp: + file: "/sys/block/${DEVICE_NAME}/device/hwmon*/temp1_input" + divider: 1000 fan_control: # set fan mode to full @@ -111,12 +114,13 @@ fan_control: to: 100 disk_power_status: - check_command: "/sbin/hdparm -C ${DEVICE}" - active: "active/idle" - standby: "standby" - sleeping: "sleeping" - standby_command: "/sbin/hdparm -y ${DEVICE}" - timeout: 5s + hdparm: + check_command: "/sbin/hdparm -C ${DEVICE}" + active: "active/idle" + standby: "standby" + sleeping: "sleeping" + standby_command: "/sbin/hdparm -y ${DEVICE}" + timeout: 5s disk_pools: rpool: @@ -131,33 +135,33 @@ disk_pools: disks: rpool1: device: "/dev/disk/by-id/ata-SAMSUNG_SSD_first" - temperature_sensor: drivetemp + temperature_sensor: satatemp monitor_temperature: always rpool2: device: "/dev/disk/by-id/ata-SAMSUNG_SSD_second" - temperature_sensor: drivetemp + temperature_sensor: satatemp monitor_temperature: always datapool1: device: "/dev/disk/by-id/ata-ST2000DM001-first" - temperature_sensor: drivetemp + temperature_sensor: satatemp monitor_temperature: when_active last_active: 50m standby_after: 1h datapool2: device: "/dev/disk/by-id/ata-ST2000DM001-second" - temperature_sensor: drivetemp + temperature_sensor: satatemp monitor_temperature: when_active last_active: 50m standby_after: 1h datapool3: device: "/dev/disk/by-id/ata-ST2000DM001-third" - temperature_sensor: drivetemp + temperature_sensor: satatemp monitor_temperature: when_active last_active: 50m standby_after: 1h datapool4: device: "/dev/disk/by-id/ata-ST2000DM001-fourth" - temperature_sensor: drivetemp + temperature_sensor: satatemp monitor_temperature: when_active last_active: 50m standby_after: 1h diff --git a/go.mod b/go.mod index b5ff32c..bba6a5f 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module github.com/creativeprojects/hardware-events -go 1.18 +go 1.19 require ( - github.com/coreos/go-systemd/v22 v22.3.2 - github.com/creativeprojects/clog v0.11.1 - github.com/stretchr/testify v1.7.5 + github.com/coreos/go-systemd/v22 v22.5.0 + github.com/creativeprojects/clog v0.13.0 + github.com/stretchr/testify v1.8.2 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect - golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect + golang.org/x/sys v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 89b956a..984ce6d 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creativeprojects/clog v0.11.1 h1:p285AvArKocGzyzlSsFvzw5Lp1ryU8m5A+ZNBC0CIHo= -github.com/creativeprojects/clog v0.11.1/go.mod h1:JmsbtbGXiZ/4muT/39UUZXWbI9sW6fNCY0DhyY6xqHQ= +github.com/creativeprojects/clog v0.13.0 h1:QVWO65Za2/XfCQeNnDvO4td9i7fwV2xuv8V5jzFIONk= +github.com/creativeprojects/clog v0.13.0/go.mod h1:4CBBbXkwjxTSIE/OyrC77aTrEEHj6daYGFEDrNjfOkU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -16,12 +16,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -30,15 +29,14 @@ github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XF github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/hardware-events.service b/hardware-events.service index 0929a65..02f1a66 100644 --- a/hardware-events.service +++ b/hardware-events.service @@ -1,5 +1,6 @@ [Unit] Description=Hardware Events Dispatcher +After=network.target [Service] Type=notify diff --git a/lib/disk.go b/lib/disk.go index daa816f..e8e19ca 100644 --- a/lib/disk.go +++ b/lib/disk.go @@ -14,28 +14,43 @@ import ( // Disk activity and status type Disk struct { - global *Global - config cfg.Disk - Name string - Device string - active *cache.CacheValue[int] - temperature *cache.CacheValue[int] - lastActivity time.Time - activityMutex sync.Mutex - stats *Diskstats - idleAfter time.Duration - standbyAfter time.Duration - diskStatusCommand DiskStatuser + global *Global + config cfg.Disk + Name string + Device string + active *cache.CacheValue[int] + temperature *cache.CacheValue[int] + lastActivity time.Time + activityMutex sync.Mutex + stats *Diskstats + idleAfter time.Duration + standbyAfter time.Duration + diskStatus DiskStatuser } // NewDisk creates a new disk activity and status monitor -func NewDisk(global *Global, name string, config cfg.Disk, diskStatusCommand DiskStatuser) (*Disk, error) { +func NewDisk(global *Global, name string, config cfg.Disk, diskStatuses map[string]DiskStatuser) (*Disk, error) { device := config.Device fi, err := os.Lstat(device) if err != nil { return nil, err } + var diskStatus DiskStatuser + if config.PowerStatus != "" { + diskStatus = diskStatuses[config.PowerStatus] + } + if diskStatus == nil && len(diskStatuses) == 1 { + // load the only one instead + for _, value := range diskStatuses { + diskStatus = value + break + } + } + if diskStatus == nil { + clog.Warningf("disk %q has no power status available", name) + } + if fi.Mode()&os.ModeSymlink != 0 { // resolve symlink into kernel device name dest, err := os.Readlink(device) @@ -67,27 +82,27 @@ func NewDisk(global *Global, name string, config cfg.Disk, diskStatusCommand Dis clog.Debugf("device %s: %s", name, device) return &Disk{ - global: global, - config: config, - Name: name, - Device: device, - active: cache.NewCacheValue[int](1 * time.Minute), - temperature: cache.NewCacheValue[int](1 * time.Minute), - idleAfter: idleAfter, - standbyAfter: standbyAfter, - diskStatusCommand: diskStatusCommand, - activityMutex: sync.Mutex{}, + global: global, + config: config, + Name: name, + Device: device, + active: cache.NewCacheValue[int](1 * time.Minute), + temperature: cache.NewCacheValue[int](1 * time.Minute), + idleAfter: idleAfter, + standbyAfter: standbyAfter, + diskStatus: diskStatus, + activityMutex: sync.Mutex{}, }, nil } // IsActive returns true when the disk is not in standby or sleep mode func (d *Disk) IsActive() bool { - if d.diskStatusCommand == nil { + if d.diskStatus == nil { // shouldn't happen? return false } output, err := d.active.Get(func() (int, error) { - return int(d.diskStatusCommand.Get(d.expandEnv)), nil + return int(d.diskStatus.Get(d.expandEnv)), nil }) if err != nil { return false @@ -191,7 +206,7 @@ func (d *Disk) StartStandbyWatch() { if d.IsActive() { if d.LastActivity().Add(d.standbyAfter).Before(time.Now()) { // time to put the disk to sleep - d.diskStatusCommand.Standby(d.expandEnv) + d.diskStatus.Standby(d.expandEnv) d.active.Set(int(enum.DiskStatusActive)) } } diff --git a/lib/disk_status.go b/lib/disk_status.go index 77aabf2..262b29a 100644 --- a/lib/disk_status.go +++ b/lib/disk_status.go @@ -2,10 +2,13 @@ package lib import ( "errors" + "os" + "path/filepath" "strings" "sync" "time" + "github.com/creativeprojects/clog" "github.com/creativeprojects/hardware-events/cfg" "github.com/creativeprojects/hardware-events/lib/enum" ) @@ -17,15 +20,18 @@ type DiskStatuser interface { type DiskStatus struct { checkCommand CommandRunner + Name string DiskActive string DiskStandby string DiskSleeping string + File string standbyCommand CommandRunner mutex sync.Mutex } -func NewDiskStatus(config cfg.DiskPowerStatus) (*DiskStatus, error) { +func NewDiskStatus(name string, config cfg.DiskPowerStatus) (*DiskStatus, error) { var timeout time.Duration + var checkCommand, standbyCommand *Command var err error if config.Timeout != "" { @@ -35,21 +41,27 @@ func NewDiskStatus(config cfg.DiskPowerStatus) (*DiskStatus, error) { } } - checkCommand, err := NewCommand(config.CheckCommand, "", timeout) - if err != nil { - return nil, err + if config.CheckCommand != "" { + checkCommand, err = NewCommand(config.CheckCommand, "", timeout) + if err != nil { + return nil, err + } } - standbyCommand, err := NewCommand(config.StandbyCommand, "", timeout) - if err != nil { - return nil, err + if config.StandbyCommand != "" { + standbyCommand, err = NewCommand(config.StandbyCommand, "", timeout) + if err != nil { + return nil, err + } } return &DiskStatus{ checkCommand: checkCommand, + Name: name, DiskActive: config.Active, DiskStandby: config.Standby, DiskSleeping: config.Sleeping, + File: config.File, standbyCommand: standbyCommand, mutex: sync.Mutex{}, }, nil @@ -59,12 +71,38 @@ func (s *DiskStatus) Get(expandEnv func(string) string) enum.DiskStatus { s.mutex.Lock() defer s.mutex.Unlock() - if s.checkCommand == nil { + var output string + var err error + + if s.checkCommand == nil && len(s.File) == 0 { return enum.DiskStatusUnknown } - output, err := s.checkCommand.Run(nil, expandEnv) - if err != nil { - return enum.DiskStatusUnknown + + // we check that the interface value is not nil, then that the implementation value is not nil + if s.checkCommand != nil && s.checkCommand.(*Command) != nil { + output, err = s.checkCommand.Run(nil, expandEnv) + if err != nil { + return enum.DiskStatusUnknown + } + } else if len(s.File) > 0 { + // we want to reference and use a Sensor instead of copying this code (from Sensor) + // resolve file name + filename := os.Expand(s.File, expandEnv) + matches, err := filepath.Glob(filename) + if err != nil { + return enum.DiskStatusUnknown + } + if len(matches) != 1 { + return enum.DiskStatusUnknown + } + filename = matches[0] + // that's a bit much :) + clog.Tracef("reading file %q", filename) + bytesOutput, err := os.ReadFile(filename) + if err != nil { + return enum.DiskStatusUnknown + } + output = strings.TrimSpace(string(bytesOutput)) } if strings.Contains(output, s.DiskActive) { return enum.DiskStatusActive @@ -82,7 +120,8 @@ func (s *DiskStatus) Standby(expandEnv func(string) string) error { s.mutex.Lock() defer s.mutex.Unlock() - if s.standbyCommand == nil { + // we check that the interface value is nil, or that the implementation value is nil + if s.standbyCommand == nil || s.standbyCommand.(*Command) == nil { return errors.New("no command defined to put the disk in standby mode") } _, err := s.standbyCommand.Run(nil, expandEnv) diff --git a/lib/disk_status_test.go b/lib/disk_status_test.go new file mode 100644 index 0000000..a8ebf99 --- /dev/null +++ b/lib/disk_status_test.go @@ -0,0 +1,33 @@ +package lib + +import ( + "testing" + + "github.com/creativeprojects/hardware-events/cfg" + "github.com/creativeprojects/hardware-events/lib/enum" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCanReadStatusFromFile(t *testing.T) { + diskStatus, err := NewDiskStatus("test", cfg.DiskPowerStatus{ + File: "fs_test_files/sys/block/nvme0n1/device/state", + }) + require.NoError(t, err) + + assert.NotNil(t, diskStatus) + assert.Equal(t, enum.DiskStatusActive, diskStatus.Get(func(s string) string { + return s + })) +} + +func TestNoStandbyIsReturningAnError(t *testing.T) { + diskStatus, err := NewDiskStatus("test", cfg.DiskPowerStatus{}) + require.NoError(t, err) + + assert.NotNil(t, diskStatus) + err = diskStatus.Standby(func(s string) string { + return s + }) + assert.ErrorContains(t, err, "no command defined to put the disk in standby mode") +} diff --git a/lib/disk_test.go b/lib/disk_test.go index 89c8e1c..a0ad738 100644 --- a/lib/disk_test.go +++ b/lib/disk_test.go @@ -13,7 +13,7 @@ import ( func TestDiskDevice(t *testing.T) { // test that a full path can be resolved into a 3 letters device (sda) - const diskByIdPath = "drivetemp_test_files/dev/disk/by-id/" + const diskByIdPath = "fs_test_files/dev/disk/by-id/" entries, err := os.ReadDir(diskByIdPath) require.NoError(t, err) diff --git a/lib/drivetemp_test_files/dev/disk/by-id/ata-ST2000DM001-first b/lib/fs_test_files/dev/disk/by-id/ata-ST2000DM001-first similarity index 100% rename from lib/drivetemp_test_files/dev/disk/by-id/ata-ST2000DM001-first rename to lib/fs_test_files/dev/disk/by-id/ata-ST2000DM001-first diff --git a/lib/drivetemp_test_files/dev/disk/by-id/ata-ST2000DM001-second b/lib/fs_test_files/dev/disk/by-id/ata-ST2000DM001-second similarity index 100% rename from lib/drivetemp_test_files/dev/disk/by-id/ata-ST2000DM001-second rename to lib/fs_test_files/dev/disk/by-id/ata-ST2000DM001-second diff --git a/lib/drivetemp_test_files/dev/sda b/lib/fs_test_files/dev/sda similarity index 100% rename from lib/drivetemp_test_files/dev/sda rename to lib/fs_test_files/dev/sda diff --git a/lib/drivetemp_test_files/dev/sdb b/lib/fs_test_files/dev/sdb similarity index 100% rename from lib/drivetemp_test_files/dev/sdb rename to lib/fs_test_files/dev/sdb diff --git a/lib/fs_test_files/sys/block/nvme0n1/device/hwmon1/temp1_input b/lib/fs_test_files/sys/block/nvme0n1/device/hwmon1/temp1_input new file mode 100644 index 0000000..62cd698 --- /dev/null +++ b/lib/fs_test_files/sys/block/nvme0n1/device/hwmon1/temp1_input @@ -0,0 +1 @@ +35850 \ No newline at end of file diff --git a/lib/fs_test_files/sys/block/nvme0n1/device/state b/lib/fs_test_files/sys/block/nvme0n1/device/state new file mode 100644 index 0000000..5fce625 --- /dev/null +++ b/lib/fs_test_files/sys/block/nvme0n1/device/state @@ -0,0 +1 @@ +live \ No newline at end of file diff --git a/lib/drivetemp_test_files/sys/block/sda/device/hwmon/hwmon4/temp1_input b/lib/fs_test_files/sys/block/sda/device/hwmon/hwmon4/temp1_input similarity index 100% rename from lib/drivetemp_test_files/sys/block/sda/device/hwmon/hwmon4/temp1_input rename to lib/fs_test_files/sys/block/sda/device/hwmon/hwmon4/temp1_input diff --git a/lib/drivetemp_test_files/sys/block/sdb/device/hwmon/hwmon5/temp1_input b/lib/fs_test_files/sys/block/sdb/device/hwmon/hwmon5/temp1_input similarity index 100% rename from lib/drivetemp_test_files/sys/block/sdb/device/hwmon/hwmon5/temp1_input rename to lib/fs_test_files/sys/block/sdb/device/hwmon/hwmon5/temp1_input diff --git a/lib/global.go b/lib/global.go index 19eec2b..f44c914 100644 --- a/lib/global.go +++ b/lib/global.go @@ -19,7 +19,7 @@ type Global struct { Templates map[string]*Template Tasks map[string]*Task Schedules map[string]*Schedule - DiskStatus DiskStatuser + DiskStatuses map[string]DiskStatuser TemperatureSensors map[string]SensorGetter FanControl *Control templ *template.Template @@ -37,6 +37,7 @@ func NewGlobal(config cfg.Config) (*Global, error) { Tasks: make(map[string]*Task, len(config.Tasks)), TemperatureSensors: make(map[string]SensorGetter, len(config.Sensors)), Schedules: make(map[string]*Schedule, len(config.Schedule)), + DiskStatuses: make(map[string]DiskStatuser, len(config.DiskPowerStatus)), diskstatsMutex: sync.Mutex{}, } @@ -46,24 +47,26 @@ func NewGlobal(config cfg.Config) (*Global, error) { } // Disk Power Status - if config.DiskPowerStatus.CheckCommand != "" { + for name, value := range config.DiskPowerStatus { var diskStatus DiskStatuser if config.Simulation { - diskStatus, err = simulation.NewDiskStatus(config.DiskPowerStatus) + diskStatus, err = simulation.NewDiskStatus(name, value) } else { - diskStatus, err = NewDiskStatus(config.DiskPowerStatus) + diskStatus, err = NewDiskStatus(name, value) } if err != nil { return global, err } - global.DiskStatus = diskStatus + global.DiskStatuses[name] = diskStatus } // Disks for name, value := range config.Disks { - disk, err := NewDisk(global, name, value, global.DiskStatus) + disk, err := NewDisk(global, name, value, global.DiskStatuses) if err != nil { - return global, err + // display an error but keep going + clog.Errorf("ignoring configuration for disk %q: %s", name, err) + continue } global.Disks[name] = disk } diff --git a/lib/sensor.go b/lib/sensor.go index 903f4d0..69c97c5 100644 --- a/lib/sensor.go +++ b/lib/sensor.go @@ -1,7 +1,6 @@ package lib import ( - "bytes" "errors" "fmt" "io/fs" @@ -167,17 +166,11 @@ func (s *Sensor) getRaw(expandEnv func(string) string) ([]string, error) { } func (s *Sensor) readFile(filename string) (string, error) { - // to use fs.Open we need to strip the first "/" from the path. let's leave it for now - // file, err := s.fs.Open(filename) - file, err := os.Open(filename) + // to use fs.Readfile we need to strip the first "/" from the path. let's leave it for now + // file, err := fs.ReadFile(s.fs, filename) + output, err := os.ReadFile(filename) if err != nil { return "", err } - defer file.Close() - buffer := &bytes.Buffer{} - _, err = buffer.ReadFrom(file) - if err != nil { - return "", err - } - return strings.TrimSpace(buffer.String()), nil + return strings.TrimSpace(string(output)), nil } diff --git a/lib/simulation/disk_status.go b/lib/simulation/disk_status.go index 5d88d9e..fbea95e 100644 --- a/lib/simulation/disk_status.go +++ b/lib/simulation/disk_status.go @@ -11,14 +11,16 @@ import ( type DiskStatus struct { status map[string]enum.DiskStatus + name string checkCommand string standbyCommand string mutex sync.Mutex } -func NewDiskStatus(config cfg.DiskPowerStatus) (*DiskStatus, error) { +func NewDiskStatus(name string, config cfg.DiskPowerStatus) (*DiskStatus, error) { return &DiskStatus{ status: make(map[string]enum.DiskStatus), + name: name, checkCommand: config.CheckCommand, standbyCommand: config.StandbyCommand, mutex: sync.Mutex{}, diff --git a/main.go b/main.go index 5911db2..b6e4966 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ import ( // These fields are populated by the goreleaser build var ( - version = "0.0.2-dev" + version = "0.1.0-dev" commit = "" date = "" builtBy = ""