package client import ( "encoding/json" "fmt" "io" "net/http" "net/url" "path" "strconv" "strings" "time" "github.com/gosimple/slug" ) // HardwareType type // https://github.com/openhardwaremonitor/openhardwaremonitor/blob/e199e0ccc69b4da92495266ebc0faf7daad97608/Hardware/IHardware.cs type HardwareType int const ( // UnknownHardware type UnknownHardware HardwareType = iota // Mainboard type Mainboard // SuperIO type SuperIO // CPU type CPU // RAM type RAM // GpuNvidia type GpuNvidia // GpuAti type GpuAti // TBalancer type TBalancer // Heatmaster type Heatmaster // HDD type HDD // Controller type for TBalancer or Heatmaster Controller // Computer type for host Computer ) // Hardware value type Hardware struct { Type HardwareType Value string TypeIndex int TypeCount int } // Sensor for unit // https://github.com/openhardwaremonitor/openhardwaremonitor/blob/e199e0ccc69b4da92495266ebc0faf7daad97608/Hardware/ISensor.cs type Sensor int const ( // UnknownSensor in V UnknownSensor Sensor = iota // Voltage in V Voltage // Clock in MHz Clock // Temperature in °C Temperature // Load in % Load // Fan in RPM Fan // Flow in L/h Flow // Control in % Control // Level in % Level // Factor in 1 Factor // Power in W Power // Data in GB = 2^30 Bytes Data // SmallData in MB = 2^20 Bytes SmallData ) var hardwareToMetric = map[HardwareType]string{ UnknownHardware: "unknown", Mainboard: "mainboard", SuperIO: "superio", CPU: "cpu", RAM: "ram", GpuNvidia: "gpu", GpuAti: "gpu", TBalancer: "tbalancer", Heatmaster: "heatmaster", HDD: "hdd", Controller: "controller", Computer: "host", } var sensorToMetric = map[Sensor]string{ UnknownSensor: "unknown", Voltage: "voltage", Clock: "mhz", Temperature: "temp", Load: "load", Fan: "rpm", Flow: "flow", Control: "control", Level: "level", Factor: "factor", Power: "power", Data: "gb", SmallData: "mb", } var imageToHardware = map[string]HardwareType{ "ati.png": GpuAti, // "bigng.png": Heatmaster, // "bigng.png": TBalancer, "bigng.png": Controller, "chip.png": SuperIO, "cpu.png": CPU, "hdd.png": HDD, "mainboard.png": Mainboard, "nvidia.png": GpuNvidia, "ram.png": RAM, "computer.png": Computer, } var sensorToType = map[string]Sensor{ "Clocks": Clock, "Controls": Control, "Data": Data, "Factors": Factor, "Fans": Fan, "Flows": Flow, "Levels": Level, "Load": Load, "Powers": Power, "Temperatures": Temperature, "Voltages": Voltage, } func (s Sensor) String() string { switch s { case Clock: return "Clock" case Control: return "Control" case Data: return "Data" case Factor: return "Factor" case Fan: return "Fan" case Flow: return "Flow" case Level: return "Level" case Load: return "Load" case Power: return "Power" case SmallData: return "SmallData" case Temperature: return "Temperature" case Voltage: return "Voltage" } return "Unknown" } func (h HardwareType) String() string { switch h { case UnknownHardware: return "Unknown" case Mainboard: return "Mainboard" case SuperIO: return "SuperIO" case CPU: return "CPU" case RAM: return "RAM" case GpuNvidia: return "GpuNvidia" case GpuAti: return "GpuAti" case TBalancer: return "TBalancer" case Heatmaster: return "Heatmaster" case HDD: return "HDD" case Controller: return "Controller" case Computer: return "Computer" } return "Unknown" } // Value from ohwm type Value struct { Hardware []Hardware Unit Sensor Label string Value string } // Float64 gets value as float64 func (v *Value) Float64() float64 { val := strings.Split(v.Value, " ") if len(val) == 0 { return 0 } ret, err := strconv.ParseFloat(val[0], 64) if err != nil { return 0 } return ret } // MetricName returns value as a metric name func (v *Value) MetricName() string { segments := []string{"ohwm"} for _, hw := range v.Hardware { metricname, ok := hardwareToMetric[hw.Type] if !ok { metricname = hardwareToMetric[UnknownHardware] } segments = append(segments, metricname) if hw.TypeCount > 1 { segments = append(segments, fmt.Sprintf("%d", hw.TypeIndex)) } } sensorname, ok := sensorToMetric[v.Unit] if !ok { sensorname = sensorToMetric[UnknownSensor] } segments = append(segments, sensorname) sluglabel := slug.Make(v.Label) segments = append(segments, sluglabel) ret := strings.Join(segments, "_") return strings.ReplaceAll(ret, "-", "_") } // IsValue true if node has children func (j *JSON) IsValue() bool { if j.Children == nil { return false } return len(j.Children) == 0 } // IsRoot returns true if json is a root node func (j *JSON) IsRoot() bool { return j.ID == 0 } // IsSensor returns true if json node is for a sensor type func (j *JSON) IsSensor() bool { if j.ImageURL == "" || j.Text == "" { return false } _, ok := sensorToType[j.Text] return ok } // SensorType returns type of sensor func (j *JSON) SensorType() Sensor { st, _ := sensorToType[j.Text] // Figure out if data is in GB (Data) or MB (SmallData) if st == Data { if len(j.Children) > 0 { val := j.Children[0] if strings.HasSuffix(val.Value, "MB") { st = SmallData } } } return st } // IsHardware returns true if json node is for hardware func (j *JSON) IsHardware() bool { if j.ImageURL == "" || j.Text == "" { return false } _, ok := imageToHardware[path.Base(j.ImageURL)] return ok } // HardwareType returns HardwareType of node func (j *JSON) HardwareType() HardwareType { ht, _ := imageToHardware[path.Base(j.ImageURL)] return ht } // Visitor callback function type Visitor func(v Value) error // Walk from json root func (j *JSON) Walk(fn Visitor) error { return j.walk(fn, Value{Hardware: []Hardware{}}, 0, 0) } func (j *JSON) childDeviceTotals() map[HardwareType]int { totals := map[HardwareType]int{} for _, child := range j.Children { totals[child.HardwareType()]++ } return totals } func (j *JSON) walk(fn Visitor, ctx Value, hwIndex, hwTotal int) error { if j.IsValue() { ctx.Label = j.Text ctx.Value = j.Value return fn(ctx) } if j.IsSensor() { ctx.Unit = j.SensorType() } if j.IsHardware() { ctx.Hardware = append(ctx.Hardware, Hardware{ Type: j.HardwareType(), Value: j.Text, TypeIndex: hwIndex, TypeCount: hwTotal, }) } totals := j.childDeviceTotals() deviceIndex := map[HardwareType]int{} for _, child := range j.Children { deviceType := child.HardwareType() index := deviceIndex[deviceType] if err := child.walk(fn, ctx, index, totals[deviceType]); err != nil { return err } deviceIndex[deviceType]++ } return nil } // Values from json root func (j *JSON) Values() ([]Value, error) { ret := []Value{} err := j.Walk(func(val Value) error { ret = append(ret, val) return nil }) if err != nil { return nil, err } return ret, nil } // JSON from data type JSON struct { ID int `json:"id"` ImageURL string Max string Min string Text string Value string Children []JSON } // Client for open hardware monitor type Client struct { Timeout time.Duration URL url.URL } // Fetch requests func (c *Client) Fetch() (*JSON, error) { client := http.Client{Timeout: c.Timeout} resp, err := client.Get(c.URL.String()) if err != nil { return nil, err } defer resp.Body.Close() return c.Decode(resp.Body) } // Decode json func (c *Client) Decode(r io.Reader) (*JSON, error) { node := &JSON{} decoder := json.NewDecoder(r) if err := decoder.Decode(node); err != nil { return nil, err } return node, nil } // Stringify tree func (j *JSON) Stringify() string { return j.stringify(0) } func (j *JSON) stringify(indent int) string { prefix := "" for i := 0; i < indent; i++ { prefix += " " } ret := prefix + j.Text if j.Value != "" { ret += ": " + j.Value } if j.Max != "" && j.Min != "" && j.Max != "-" && j.Min != "-" { ret += fmt.Sprintf(" (%s - %s)", j.Min, j.Max) } ret += "\n" for _, child := range j.Children { ret += child.stringify(indent + 1) } return ret }