You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
8.2 KiB
Go

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
}