summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIlya Mashchenko <ilya@netdata.cloud>2024-04-21 16:13:34 +0300
committerGitHub <noreply@github.com>2024-04-21 16:13:34 +0300
commit7826adcf6ce4a9678f6cfefcc2c5edc80ab9f57e (patch)
treed780c5e693556ec0b188bba66a0cd231cef88880
parent7fdef9d8bb1bd4a1c5bdd4c218867da3af5b51d7 (diff)
go.d add hddtemp (#17462)
-rw-r--r--src/go/collectors/go.d.plugin/config/go.d.conf1
-rw-r--r--src/go/collectors/go.d.plugin/config/go.d/hddtemp.conf6
-rw-r--r--src/go/collectors/go.d.plugin/config/go.d/sd/net_listeners.conf7
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/charts.go70
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/client.go44
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/collect.go140
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/config_schema.json44
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp.go104
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp_test.go321
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/metadata.yaml134
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.json5
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.yaml3
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-ok.txt1
-rw-r--r--src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-sleep.txt1
-rw-r--r--src/go/collectors/go.d.plugin/modules/init.go1
15 files changed, 882 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/config/go.d.conf b/src/go/collectors/go.d.plugin/config/go.d.conf
index ab3a5aca74..9fe91db5d9 100644
--- a/src/go/collectors/go.d.plugin/config/go.d.conf
+++ b/src/go/collectors/go.d.plugin/config/go.d.conf
@@ -39,6 +39,7 @@ modules:
# fluentd: yes
# freeradius: yes
# haproxy: yes
+# hddtemp: yes
# hdfs: yes
# httpcheck: yes
# intelgpu: yes
diff --git a/src/go/collectors/go.d.plugin/config/go.d/hddtemp.conf b/src/go/collectors/go.d.plugin/config/go.d/hddtemp.conf
new file mode 100644
index 0000000000..a2ea8452d3
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/config/go.d/hddtemp.conf
@@ -0,0 +1,6 @@
+## All available configuration options, their descriptions and default values:
+## https://github.com/netdata/netdata/tree/master/src/go/collectors/go.d.plugin/modules/hddtemp#readme
+
+#jobs:
+# - name: local
+# address: 127.0.0.1:7634
diff --git a/src/go/collectors/go.d.plugin/config/go.d/sd/net_listeners.conf b/src/go/collectors/go.d.plugin/config/go.d/sd/net_listeners.conf
index 1c240ce5b6..6c2e22c71c 100644
--- a/src/go/collectors/go.d.plugin/config/go.d/sd/net_listeners.conf
+++ b/src/go/collectors/go.d.plugin/config/go.d/sd/net_listeners.conf
@@ -50,6 +50,8 @@ classify:
expr: '{{ and (eq .Port "6060") (eq .Comm "geth") }}'
- tags: "haproxy"
expr: '{{ and (eq .Port "8404") (eq .Comm "haproxy") }}'
+ - tags: "hddtemp"
+ expr: '{{ and (eq .Port "7634") (eq .Comm "hddtemp") }}'
- tags: "hdfs_namenode"
expr: '{{ and (eq .Port "9870") (eq .Comm "hadoop") }}'
- tags: "hdfs_datanode"
@@ -226,6 +228,11 @@ compose:
module: haproxy
name: local
url: http://{{.Address}}/metrics
+ - selector: "hddtemp"
+ template: |
+ module: hddtemp
+ name: local
+ address: {{.Address}}
- selector: "hdfs_namenode"
template: |
module: hdfs
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/charts.go b/src/go/collectors/go.d.plugin/modules/hddtemp/charts.go
new file mode 100644
index 0000000000..7a5e9ed9f4
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/charts.go
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package hddtemp
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+)
+
+const (
+ prioDiskTemperature = module.Priority + iota
+ prioDiskTemperatureSensorStatus
+)
+
+var (
+ diskTemperatureChartsTmpl = module.Chart{
+ ID: "disk_%s_temperature",
+ Title: "Disk temperature",
+ Units: "Celsius",
+ Fam: "temperature",
+ Ctx: "hddtemp.disk_temperature",
+ Type: module.Line,
+ Priority: prioDiskTemperature,
+ Dims: module.Dims{
+ {ID: "disk_%s_temperature", Name: "temperature"},
+ },
+ }
+ diskTemperatureSensorChartsTmpl = module.Chart{
+ ID: "disk_%s_temperature_sensor_status",
+ Title: "Disk temperature sensor status",
+ Units: "status",
+ Fam: "sensor",
+ Ctx: "hddtemp.disk_temperature_sensor_status",
+ Type: module.Line,
+ Priority: prioDiskTemperatureSensorStatus,
+ Dims: module.Dims{
+ {ID: "disk_%s_temp_sensor_status_ok", Name: "ok"},
+ {ID: "disk_%s_temp_sensor_status_err", Name: "err"},
+ {ID: "disk_%s_temp_sensor_status_na", Name: "na"},
+ {ID: "disk_%s_temp_sensor_status_unk", Name: "unk"},
+ {ID: "disk_%s_temp_sensor_status_nos", Name: "nos"},
+ {ID: "disk_%s_temp_sensor_status_slp", Name: "slp"},
+ },
+ }
+)
+
+func (h *HddTemp) addDiskTempSensorStatusChart(id string, disk diskStats) {
+ h.addDiskChart(id, disk, diskTemperatureSensorChartsTmpl.Copy())
+}
+
+func (h *HddTemp) addDiskTempChart(id string, disk diskStats) {
+ h.addDiskChart(id, disk, diskTemperatureChartsTmpl.Copy())
+}
+
+func (h *HddTemp) addDiskChart(id string, disk diskStats, chart *module.Chart) {
+ chart.ID = fmt.Sprintf(chart.ID, strings.ToLower(id))
+ chart.Labels = []module.Label{
+ {Key: "disk_id", Value: id},
+ {Key: "model", Value: disk.model},
+ }
+ for _, dim := range chart.Dims {
+ dim.ID = fmt.Sprintf(dim.ID, id)
+ }
+
+ if err := h.Charts().Add(chart); err != nil {
+ h.Warning(err)
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/client.go b/src/go/collectors/go.d.plugin/modules/hddtemp/client.go
new file mode 100644
index 0000000000..626381ee86
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/client.go
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package hddtemp
+
+import (
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/socket"
+)
+
+func newHddTempConn(conf Config) hddtempConn {
+ return &hddtempClient{conn: socket.New(socket.Config{
+ Address: conf.Address,
+ ConnectTimeout: conf.Timeout.Duration(),
+ ReadTimeout: conf.Timeout.Duration(),
+ WriteTimeout: conf.Timeout.Duration(),
+ })}
+}
+
+type hddtempClient struct {
+ conn socket.Client
+}
+
+func (c *hddtempClient) connect() error {
+ return c.conn.Connect()
+}
+
+func (c *hddtempClient) disconnect() {
+ _ = c.conn.Disconnect()
+}
+
+func (c *hddtempClient) queryHddTemp() (string, error) {
+ var i int
+ var s string
+ err := c.conn.Command("", func(bytes []byte) bool {
+ if i++; i > 1 {
+ return false
+ }
+ s = string(bytes)
+ return true
+ })
+ if err != nil {
+ return "", err
+ }
+ return s, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/collect.go b/src/go/collectors/go.d.plugin/modules/hddtemp/collect.go
new file mode 100644
index 0000000000..f5c75db041
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/collect.go
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package hddtemp
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+type diskStats struct {
+ devPath string
+ model string
+ temperature string
+ unit string
+}
+
+func (h *HddTemp) collect() (map[string]int64, error) {
+ conn := h.newHddTempConn(h.Config)
+
+ if err := conn.connect(); err != nil {
+ return nil, err
+ }
+
+ defer conn.disconnect()
+
+ msg, err := conn.queryHddTemp()
+ if err != nil {
+ return nil, err
+ }
+
+ h.Debugf("hddtemp daemon response: %s", msg)
+
+ disks, err := parseHddTempMessage(msg)
+ if err != nil {
+ return nil, err
+ }
+
+ mx := make(map[string]int64)
+
+ for _, disk := range disks {
+ id := getDiskID(disk)
+ if id == "" {
+ h.Debugf("can not extract disk id from '%s'", disk.devPath)
+ continue
+ }
+
+ if !h.disks[id] {
+ h.disks[id] = true
+ h.addDiskTempSensorStatusChart(id, disk)
+ }
+
+ px := fmt.Sprintf("disk_%s_", id)
+
+ for _, st := range []string{"ok", "na", "unk", "nos", "slp", "err"} {
+ mx[px+"temp_sensor_status_"+st] = 0
+ }
+ switch disk.temperature {
+ case "NA":
+ mx[px+"temp_sensor_status_na"] = 1
+ case "UNK":
+ mx[px+"temp_sensor_status_unk"] = 1
+ case "NOS":
+ mx[px+"temp_sensor_status_nos"] = 1
+ case "SLP":
+ mx[px+"temp_sensor_status_slp"] = 1
+ case "ERR":
+ mx[px+"temp_sensor_status_err"] = 1
+ default:
+ if v, ok := getTemperature(disk); ok {
+ if !h.disksTemp[id] {
+ h.disksTemp[id] = true
+ h.addDiskTempChart(id, disk)
+ }
+ mx[px+"temp_sensor_status_ok"] = 1
+ mx[px+"temperature"] = v
+ } else {
+ mx[px+"temp_sensor_status_unk"] = 1
+ }
+ }
+ }
+
+ return mx, nil
+}
+
+func getDiskID(d diskStats) string {
+ i := strings.LastIndexByte(d.devPath, '/')
+ if i == -1 {
+ return ""
+ }
+ return d.devPath[i+1:]
+}
+
+func getTemperature(d diskStats) (int64, bool) {
+ v, err := strconv.ParseInt(d.temperature, 10, 64)
+ if err != nil {
+ return 0, false
+ }
+ if d.unit == "F" {
+ v = (v - 32) * 5 / 9
+ }
+ return v, true
+}
+
+func parseHddTempMessage(msg string) ([]diskStats, error) {
+ if msg == "" {
+ return nil, errors.New("empty hddtemp message")
+ }
+
+ // https://github.com/guzu/hddtemp/blob/e16aed6d0145d7ad8b3308dd0b9199fc701c0417/src/daemon.c#L165
+ parts := strings.Split(msg, "|")
+
+ var i int
+ // remove empty values
+ for _, v := range parts {
+ if v = strings.TrimSpace(v); v != "" {
+ parts[i] = v
+ i++
+ }
+ }
+ parts = parts[:i]
+
+ if len(parts) == 0 || len(parts)%4 != 0 {
+ return nil, errors.New("invalid hddtemp output format")
+ }
+
+ var disks []diskStats
+
+ for i := 0; i < len(parts); i += 4 {
+ disks = append(disks, diskStats{
+ devPath: parts[i],
+ model: parts[i+1],
+ temperature: parts[i+2],
+ unit: parts[i+3],
+ })
+ }
+
+ return disks, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/config_schema.json b/src/go/collectors/go.d.plugin/modules/hddtemp/config_schema.json
new file mode 100644
index 0000000000..2858fbe026
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/config_schema.json
@@ -0,0 +1,44 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "HddTemp collector configuration.",
+ "type": "object",
+ "properties": {
+ "update_every": {
+ "title": "Update every",
+ "description": "Data collection interval, measured in seconds.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 1
+ },
+ "address": {
+ "title": "Address",
+ "description": "The IP address and port where the hddtemp daemon listens for connections.",
+ "type": "string",
+ "default": "127.0.0.1:7634"
+ },
+ "timeout": {
+ "title": "Timeout",
+ "description": "Timeout for establishing a connection and communication (reading and writing) in seconds.",
+ "type": "number",
+ "minimum": 0.5,
+ "default": 1
+ }
+ },
+ "required": [
+ "address"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "uiOptions": {
+ "fullPage": true
+ },
+ "timeout": {
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp.go b/src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp.go
new file mode 100644
index 0000000000..3976506053
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp.go
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package hddtemp
+
+import (
+ _ "embed"
+ "errors"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("hddtemp", module.Creator{
+ JobConfigSchema: configSchema,
+ Create: func() module.Module { return New() },
+ })
+}
+
+func New() *HddTemp {
+ return &HddTemp{
+ Config: Config{
+ Address: "127.0.0.1:7634",
+ Timeout: web.Duration(time.Second * 1),
+ },
+ newHddTempConn: newHddTempConn,
+ charts: &module.Charts{},
+ disks: make(map[string]bool),
+ disksTemp: make(map[string]bool),
+ }
+}
+
+type Config struct {
+ UpdateEvery int `yaml:"update_every" json:"update_every"`
+ Address string `yaml:"address" json:"address"`
+ Timeout web.Duration `yaml:"timeout" json:"timeout"`
+}
+
+type (
+ HddTemp struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ charts *module.Charts
+
+ newHddTempConn func(Config) hddtempConn
+
+ disks map[string]bool
+ disksTemp map[string]bool
+ }
+
+ hddtempConn interface {
+ connect() error
+ disconnect()
+ queryHddTemp() (string, error)
+ }
+)
+
+func (h *HddTemp) Configuration() any {
+ return h.Config
+}
+
+func (h *HddTemp) Init() error {
+ if h.Address == "" {
+ h.Error("config: 'address' not set")
+ return errors.New("address not set")
+ }
+
+ return nil
+}
+
+func (h *HddTemp) Check() error {
+ mx, err := h.collect()
+ if err != nil {
+ h.Error(err)
+ return err
+ }
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+ return nil
+}
+
+func (h *HddTemp) Charts() *module.Charts {
+ return h.charts
+}
+
+func (h *HddTemp) Collect() map[string]int64 {
+ mx, err := h.collect()
+ if err != nil {
+ h.Error(err)
+ }
+
+ if len(mx) == 0 {
+ return nil
+ }
+ return mx
+}
+
+func (h *HddTemp) Cleanup() {}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp_test.go b/src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp_test.go
new file mode 100644
index 0000000000..cab4ceb970
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/hddtemp_test.go
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package hddtemp
+
+import (
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+
+ dataAllOK, _ = os.ReadFile("testdata/hddtemp-all-ok.txt")
+ dataAllSleep, _ = os.ReadFile("testdata/hddtemp-all-sleep.txt")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+
+ "dataAllOK": dataAllOK,
+ "dataAllSleep": dataAllSleep,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestHddTemp_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &HddTemp{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestHddTemp_Init(t *testing.T) {
+ tests := map[string]struct {
+ config Config
+ wantFail bool
+ }{
+ "success with default config": {
+ wantFail: false,
+ config: New().Config,
+ },
+ "fails if address not set": {
+ wantFail: true,
+ config: func() Config {
+ conf := New().Config
+ conf.Address = ""
+ return conf
+ }(),
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ hdd := New()
+ hdd.Config = test.config
+
+ if test.wantFail {
+ assert.Error(t, hdd.Init())
+ } else {
+ assert.NoError(t, hdd.Init())
+ }
+ })
+ }
+}
+
+func TestHddTemp_Cleanup(t *testing.T) {
+ tests := map[string]struct {
+ prepare func() *HddTemp
+ }{
+ "not initialized": {
+ prepare: func() *HddTemp {
+ return New()
+ },
+ },
+ "after check": {
+ prepare: func() *HddTemp {
+ hdd := New()
+ hdd.newHddTempConn = func(config Config) hddtempConn { return prepareMockAllDisksOk() }
+ _ = hdd.Check()
+ return hdd
+ },
+ },
+ "after collect": {
+ prepare: func() *HddTemp {
+ hdd := New()
+ hdd.newHddTempConn = func(config Config) hddtempConn { return prepareMockAllDisksOk() }
+ _ = hdd.Collect()
+ return hdd
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ hdd := test.prepare()
+
+ assert.NotPanics(t, hdd.Cleanup)
+ })
+ }
+}
+
+func TestHddTemp_Charts(t *testing.T) {
+ assert.NotNil(t, New().Charts())
+}
+
+func TestHddTemp_Check(t *testing.T) {
+ tests := map[string]struct {
+ prepareMock func() *mockHddTempConn
+ wantFail bool
+ }{
+ "all disks ok": {
+ wantFail: false,
+ prepareMock: prepareMockAllDisksOk,
+ },
+ "all disks sleep": {
+ wantFail: false,
+ prepareMock: prepareMockAllDisksSleep,
+ },
+ "err on connect": {
+ wantFail: true,
+ prepareMock: prepareMockErrOnConnect,
+ },
+ "unexpected response": {
+ wantFail: true,
+ prepareMock: prepareMockUnexpectedResponse,
+ },
+ "empty response": {
+ wantFail: true,
+ prepareMock: prepareMockEmptyResponse,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ hdd := New()
+ mock := test.prepareMock()
+ hdd.newHddTempConn = func(config Config) hddtempConn { return mock }
+
+ if test.wantFail {
+ assert.Error(t, hdd.Check())
+ } else {
+ assert.NoError(t, hdd.Check())
+ }
+ })
+ }
+}
+
+func TestHddTemp_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepareMock func() *mockHddTempConn
+ wantMetrics map[string]int64
+ wantDisconnect bool
+ wantCharts int
+ }{
+ "all disks ok": {
+ prepareMock: prepareMockAllDisksOk,
+ wantDisconnect: true,
+ wantCharts: 2 * 4,
+ wantMetrics: map[string]int64{
+ "disk_sda_temp_sensor_status_err": 0,
+ "disk_sda_temp_sensor_status_na": 0,
+ "disk_sda_temp_sensor_status_nos": 0,
+ "disk_sda_temp_sensor_status_ok": 1,
+ "disk_sda_temp_sensor_status_slp": 0,
+ "disk_sda_temp_sensor_status_unk": 0,
+ "disk_sda_temperature": 50,
+ "disk_sdb_temp_sensor_status_err": 0,
+ "disk_sdb_temp_sensor_status_na": 0,
+ "disk_sdb_temp_sensor_status_nos": 0,
+ "disk_sdb_temp_sensor_status_ok": 1,
+ "disk_sdb_temp_sensor_status_slp": 0,
+ "disk_sdb_temp_sensor_status_unk": 0,
+ "disk_sdb_temperature": 49,
+ "disk_sdc_temp_sensor_status_err": 0,
+ "disk_sdc_temp_sensor_status_na": 0,
+ "disk_sdc_temp_sensor_status_nos": 0,
+ "disk_sdc_temp_sensor_status_ok": 1,
+ "disk_sdc_temp_sensor_status_slp": 0,
+ "disk_sdc_temp_sensor_status_unk": 0,
+ "disk_sdc_temperature": 27,
+ "disk_sdd_temp_sensor_status_err": 0,
+ "disk_sdd_temp_sensor_status_na": 0,
+ "disk_sdd_temp_sensor_status_nos": 0,
+ "disk_sdd_temp_sensor_status_ok": 1,
+ "disk_sdd_temp_sensor_status_slp": 0,
+ "disk_sdd_temp_sensor_status_unk": 0,
+ "disk_sdd_temperature": 29,
+ },
+ },
+ "all disks sleep": {
+ prepareMock: prepareMockAllDisksSleep,
+ wantDisconnect: true,
+ wantCharts: 3,
+ wantMetrics: map[string]int64{
+ "disk_ata-HUP722020APA330_BFGWU7WF_temp_sensor_status_err": 0,
+ "disk_ata-HUP722020APA330_BFGWU7WF_temp_sensor_status_na": 0,
+ "disk_ata-HUP722020APA330_BFGWU7WF_temp_sensor_status_nos": 0,
+ "disk_ata-HUP722020APA330_BFGWU7WF_temp_sensor_status_ok": 0,
+ "disk_ata-HUP722020APA330_BFGWU7WF_temp_sensor_status_slp": 1,
+ "disk_ata-HUP722020APA330_BFGWU7WF_temp_sensor_status_unk": 0,
+ "disk_ata-HUP722020APA330_BFJ0WS3F_temp_sensor_status_err": 0,
+ "disk_ata-HUP722020APA330_BFJ0WS3F_temp_sensor_status_na": 0,
+ "disk_ata-HUP722020APA330_BFJ0WS3F_temp_sensor_status_nos": 0,
+ "disk_ata-HUP722020APA330_BFJ0WS3F_temp_sensor_status_ok": 0,
+ "disk_ata-HUP722020APA330_BFJ0WS3F_temp_sensor_status_slp": 1,
+ "disk_ata-HUP722020APA330_BFJ0WS3F_temp_sensor_status_unk": 0,
+ "disk_ata-WDC_WD10EARS-00Y5B1_WD-WCAV5R693922_temp_sensor_status_err": 0,
+ "disk_ata-WDC_WD10EARS-00Y5B1_WD-WCAV5R693922_temp_sensor_status_na": 0,
+ "disk_ata-WDC_WD10EARS-00Y5B1_WD-WCAV5R693922_temp_sensor_status_nos": 0,
+ "disk_ata-WDC_WD10EARS-00Y5B1_WD-WCAV5R693922_temp_sensor_status_ok": 0,
+ "disk_ata-WDC_WD10EARS-00Y5B1_WD-WCAV5R693922_temp_sensor_status_slp": 1,
+ "disk_ata-WDC_WD10EARS-00Y5B1_WD-WCAV5R693922_temp_sensor_status_unk": 0,
+ },
+ },
+ "err on connect": {
+ prepareMock: prepareMockErrOnConnect,
+ wantDisconnect: false,
+ },
+ "unexpected response": {
+ prepareMock: prepareMockUnexpectedResponse,
+ wantDisconnect: true,
+ },
+ "empty response": {
+ prepareMock: prepareMockEmptyResponse,
+ wantDisconnect: true,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ hdd := New()
+ mock := test.prepareMock()
+ hdd.newHddTempConn = func(config Config) hddtempConn { return mock }
+
+ mx := hdd.Collect()
+
+ assert.Equal(t, test.wantMetrics, mx)
+ assert.Len(t, *hdd.Charts(), test.wantCharts)
+ assert.Equal(t, test.wantDisconnect, mock.disconnectCalled)
+ testMetricsHasAllChartsDims(t, hdd, mx)
+ })
+ }
+}
+
+func testMetricsHasAllChartsDims(t *testing.T, hdd *HddTemp, mx map[string]int64) {
+ for _, chart := range *hdd.Charts() {
+ if chart.Obsolete {
+ continue
+ }
+ for _, dim := range chart.Dims {
+ _, ok := mx[dim.ID]
+ assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
+ }
+ for _, v := range chart.Vars {
+ _, ok := mx[v.ID]
+ assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
+ }
+ }
+}
+
+func prepareMockAllDisksOk() *mockHddTempConn {
+ return &mockHddTempConn{
+ hddTempLine: string(dataAllOK),
+ }
+}
+
+func prepareMockAllDisksSleep() *mockHddTempConn {
+ return &mockHddTempConn{
+ hddTempLine: string(dataAllSleep),
+ }
+}
+
+func prepareMockErrOnConnect() *mockHddTempConn {
+ return &mockHddTempConn{
+ errOnConnect: true,
+ }
+}
+
+func prepareMockUnexpectedResponse() *mockHddTempConn {
+ return &mockHddTempConn{
+ hddTempLine: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ }
+}
+
+func prepareMockEmptyResponse() *mockHddTempConn {
+ return &mockHddTempConn{
+ hddTempLine: "",
+ }
+}
+
+type mockHddTempConn struct {
+ errOnConnect bool
+ errOnQueryHddTemp bool
+ hddTempLine string
+ disconnectCalled bool
+}
+
+func (m *mockHddTempConn) connect() error {
+ if m.errOnConnect {
+ return errors.New("mock.connect() error")
+ }
+ return nil
+}
+
+func (m *mockHddTempConn) disconnect() {
+ m.disconnectCalled = true
+}
+
+func (m *mockHddTempConn) queryHddTemp() (string, error) {
+ if m.errOnQueryHddTemp {
+ return "", errors.New("mock.queryHddTemp() error")
+ }
+ return m.hddTempLine, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/metadata.yaml b/src/go/collectors/go.d.plugin/modules/hddtemp/metadata.yaml
new file mode 100644
index 0000000000..74206ebc95
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/metadata.yaml
@@ -0,0 +1,134 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-hddtemp
+ plugin_name: go.d.plugin
+ module_name: hddtemp
+ monitored_instance:
+ name: HDD temperature
+ link: https://linux.die.net/man/8/hddtemp
+ categories:
+ - data-collection.hardware-devices-and-sensors
+ icon_filename: "hard-drive.svg"
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ keywords:
+ - hardware
+ - hdd temperature
+ - disk temperature
+ - temperature
+ most_popular: false
+ overview:
+ data_collection:
+ metrics_description: |
+ This collector monitors disk temperatures.
+ method_description: |
+ It retrieves temperature data for attached disks by querying the hddtemp daemon at regular intervals.
+ supported_platforms:
+ include:
+ - Linux
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: By default, this collector will attempt to connect to the `hddtemp` daemon on `127.0.0.1:7634`
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list:
+ - title: Install hddtemp
+ description: |
+ Install `hddtemp` using your distribution's package manager.
+ configuration:
+ file:
+ name: go.d/hddtemp.conf
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: 1
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: 0
+ required: false
+ - name: address
+ description: The IP address and port where the hddtemp daemon listens for connections.
+ default_value: 127.0.0.1:7634
+ required: true
+ - name: timeout
+ description: Connection, read, and write timeout duration in seconds. The timeout includes name resolution.
+ default_value: 1
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: Basic
+ description: A basic example configuration.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:7634
+ - name: Multi-instance
+ description: |
+ > **Note**: When you define multiple jobs, their names must be unique.
+
+ Collecting metrics from local and remote instances.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:7634
+
+ - name: remote
+ address: 203.0.113.0:7634
+ troubleshooting:
+ problems:
+ list: []
+ alerts: []
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: ""
+ availability: []
+ scopes:
+ - name: disk
+ description: These metrics refer to the Disk.
+ labels:
+ - name: disk_id
+ description: Disk identifier. It is derived from the device path (e.g. sda or ata-HUP722020APA330_BFJ0WS3F)
+ - name: model
+ description: Disk model
+ metrics:
+ - name: hddtemp.disk_temperature
+ description: Disk temperature
+ unit: Celsius
+ chart_type: line
+ dimensions:
+ - name: temperature
+ - name: hddtemp.disk_temperature_sensor_status
+ description: Disk temperature sensor status
+ unit: status
+ chart_type: line
+ dimensions:
+ - name: ok
+ - name: err
+ - name: na
+ - name: unk
+ - name: nos
+ - name: slp
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.json b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.json
new file mode 100644
index 0000000000..e868347203
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.json
@@ -0,0 +1,5 @@
+{
+ "update_every": 123,
+ "address": "ok",
+ "timeout": 123.123
+}
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.yaml
new file mode 100644
index 0000000000..1b81d09eb8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/config.yaml
@@ -0,0 +1,3 @@
+update_every: 123
+address: "ok"
+timeout: 123.123
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-ok.txt b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-ok.txt
new file mode 100644
index 0000000000..5f6606e812
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-ok.txt
@@ -0,0 +1 @@
+|/dev/sda|WDC WD181KRYZ-01AGBB0|122|F||/dev/sdb|WDC WD181KRYZ-01AGBB0|49|C||/dev/sdc|WDC WDS400T1R0A-68A4W0|27|C||/dev/sdd|WDC WDS400T1R0A-68A4W0|29|C| \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-sleep.txt b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-sleep.txt
new file mode 100644
index 0000000000..732b62c762
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/hddtemp/testdata/hddtemp-all-sleep.txt
@@ -0,0 +1 @@