mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2025-04-08 22:45:55 +02:00
Unit system for ClusterCockpit to use similar names everywhere and ease unit conversions
This commit is contained in:
parent
3cf2f69a07
commit
c9fb8ca327
178
internal/ccUnits/ccUnitMeasure.go
Normal file
178
internal/ccUnits/ccUnitMeasure.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package ccunits
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
type Measure int
|
||||||
|
|
||||||
|
const (
|
||||||
|
None Measure = iota
|
||||||
|
Bytes
|
||||||
|
Flops
|
||||||
|
Percentage
|
||||||
|
TemperatureC
|
||||||
|
TemperatureF
|
||||||
|
Rotation
|
||||||
|
Hertz
|
||||||
|
Time
|
||||||
|
Power
|
||||||
|
Energy
|
||||||
|
Cycles
|
||||||
|
Requests
|
||||||
|
Packets
|
||||||
|
Events
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Measure) String() string {
|
||||||
|
switch *m {
|
||||||
|
case Bytes:
|
||||||
|
return "Bytes"
|
||||||
|
case Flops:
|
||||||
|
return "Flops"
|
||||||
|
case Percentage:
|
||||||
|
return "Percent"
|
||||||
|
case TemperatureC:
|
||||||
|
return "DegreeC"
|
||||||
|
case TemperatureF:
|
||||||
|
return "DegreeF"
|
||||||
|
case Rotation:
|
||||||
|
return "RPM"
|
||||||
|
case Hertz:
|
||||||
|
return "Hertz"
|
||||||
|
case Time:
|
||||||
|
return "Seconds"
|
||||||
|
case Power:
|
||||||
|
return "Watts"
|
||||||
|
case Energy:
|
||||||
|
return "Joules"
|
||||||
|
case Cycles:
|
||||||
|
return "Cycles"
|
||||||
|
case Requests:
|
||||||
|
return "Requests"
|
||||||
|
case Packets:
|
||||||
|
return "Packets"
|
||||||
|
case Events:
|
||||||
|
return "Events"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Measure) Short() string {
|
||||||
|
switch *m {
|
||||||
|
case Bytes:
|
||||||
|
return "Bytes"
|
||||||
|
case Flops:
|
||||||
|
return "Flops"
|
||||||
|
case Percentage:
|
||||||
|
return "Percent"
|
||||||
|
case TemperatureC:
|
||||||
|
return "degC"
|
||||||
|
case TemperatureF:
|
||||||
|
return "degF"
|
||||||
|
case Rotation:
|
||||||
|
return "RPM"
|
||||||
|
case Hertz:
|
||||||
|
return "Hz"
|
||||||
|
case Time:
|
||||||
|
return "s"
|
||||||
|
case Power:
|
||||||
|
return "W"
|
||||||
|
case Energy:
|
||||||
|
return "J"
|
||||||
|
case Cycles:
|
||||||
|
return "cyc"
|
||||||
|
case Requests:
|
||||||
|
return "requests"
|
||||||
|
case Packets:
|
||||||
|
return "packets"
|
||||||
|
case Events:
|
||||||
|
return "events"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytesRegexStr = `^([bB][yY]?[tT]?[eE]?[sS]?)`
|
||||||
|
const flopsRegexStr = `^([fF][lL]?[oO]?[pP]?[sS]?)`
|
||||||
|
const percentRegexStr = `^(%%|[pP]ercent)`
|
||||||
|
const degreeCRegexStr = `^(deg[Cc]|°[cC])`
|
||||||
|
const degreeFRegexStr = `^(deg[fF]|°[fF])`
|
||||||
|
const rpmRegexStr = `^([rR][pP][mM])`
|
||||||
|
const hertzRegexStr = `^([hH][eE]?[rR]?[tT]?[zZ])`
|
||||||
|
const timeRegexStr = `^([sS][eE]?[cC]?[oO]?[nN]?[dD]?[sS]?)`
|
||||||
|
const powerRegexStr = `^([wW][aA]?[tT]?[tT]?[sS]?)`
|
||||||
|
const energyRegexStr = `^([jJ][oO]?[uU]?[lL]?[eE]?[sS]?)`
|
||||||
|
const cyclesRegexStr = `^([cC][yY][cC]?[lL]?[eE]?[sS]?)`
|
||||||
|
const requestsRegexStr = `^([rR][eE][qQ][uU]?[eE]?[sS]?[tT]?[sS]?)`
|
||||||
|
const packetsRegexStr = `^([pP][aA]?[cC]?[kK][eE]?[tT][sS]?)`
|
||||||
|
|
||||||
|
var bytesRegex = regexp.MustCompile(bytesRegexStr)
|
||||||
|
var flopsRegex = regexp.MustCompile(flopsRegexStr)
|
||||||
|
var percentRegex = regexp.MustCompile(percentRegexStr)
|
||||||
|
var degreeCRegex = regexp.MustCompile(degreeCRegexStr)
|
||||||
|
var degreeFRegex = regexp.MustCompile(degreeFRegexStr)
|
||||||
|
var rpmRegex = regexp.MustCompile(rpmRegexStr)
|
||||||
|
var hertzRegex = regexp.MustCompile(hertzRegexStr)
|
||||||
|
var timeRegex = regexp.MustCompile(timeRegexStr)
|
||||||
|
var powerRegex = regexp.MustCompile(powerRegexStr)
|
||||||
|
var energyRegex = regexp.MustCompile(energyRegexStr)
|
||||||
|
var cyclesRegex = regexp.MustCompile(cyclesRegexStr)
|
||||||
|
var requestsRegex = regexp.MustCompile(requestsRegexStr)
|
||||||
|
var packetsRegex = regexp.MustCompile(packetsRegexStr)
|
||||||
|
|
||||||
|
func NewMeasure(unit string) Measure {
|
||||||
|
var match []string
|
||||||
|
match = bytesRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Bytes
|
||||||
|
}
|
||||||
|
match = flopsRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Flops
|
||||||
|
}
|
||||||
|
match = percentRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Percentage
|
||||||
|
}
|
||||||
|
match = degreeCRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return TemperatureC
|
||||||
|
}
|
||||||
|
match = degreeFRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return TemperatureF
|
||||||
|
}
|
||||||
|
match = rpmRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Rotation
|
||||||
|
}
|
||||||
|
match = hertzRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Hertz
|
||||||
|
}
|
||||||
|
match = timeRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Time
|
||||||
|
}
|
||||||
|
match = cyclesRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Cycles
|
||||||
|
}
|
||||||
|
match = powerRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Power
|
||||||
|
}
|
||||||
|
match = energyRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Energy
|
||||||
|
}
|
||||||
|
match = requestsRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Requests
|
||||||
|
}
|
||||||
|
match = packetsRegex.FindStringSubmatch(unit)
|
||||||
|
if match != nil {
|
||||||
|
return Packets
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
131
internal/ccUnits/ccUnitScale.go
Normal file
131
internal/ccUnits/ccUnitScale.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package ccunits
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
type Scale float64
|
||||||
|
|
||||||
|
const (
|
||||||
|
Base Scale = iota
|
||||||
|
Peta = 1e15
|
||||||
|
Tera = 1e12
|
||||||
|
Giga = 1e9
|
||||||
|
Mega = 1e6
|
||||||
|
Kilo = 1e3
|
||||||
|
Milli = 1e-3
|
||||||
|
Micro = 1e-6
|
||||||
|
Nano = 1e-9
|
||||||
|
Kibi = 1024
|
||||||
|
Mebi = 1024 * 1024
|
||||||
|
Gibi = 1024 * 1024 * 1024
|
||||||
|
Tebi = 1024 * 1024 * 1024 * 1024
|
||||||
|
)
|
||||||
|
const prefixRegexStr = `^([kKmMgGtTpP]?[i]?)(.*)`
|
||||||
|
|
||||||
|
var prefixRegex = regexp.MustCompile(prefixRegexStr)
|
||||||
|
|
||||||
|
func (s *Scale) String() string {
|
||||||
|
switch *s {
|
||||||
|
case Base:
|
||||||
|
return ""
|
||||||
|
case Kilo:
|
||||||
|
return "Kilo"
|
||||||
|
case Mega:
|
||||||
|
return "Mega"
|
||||||
|
case Giga:
|
||||||
|
return "Giga"
|
||||||
|
case Tera:
|
||||||
|
return "Tera"
|
||||||
|
case Peta:
|
||||||
|
return "Peta"
|
||||||
|
case Milli:
|
||||||
|
return "Milli"
|
||||||
|
case Micro:
|
||||||
|
return "Micro"
|
||||||
|
case Nano:
|
||||||
|
return "Nano"
|
||||||
|
case Kibi:
|
||||||
|
return "Kibi"
|
||||||
|
case Mebi:
|
||||||
|
return "Mebi"
|
||||||
|
case Gibi:
|
||||||
|
return "Gibi"
|
||||||
|
case Tebi:
|
||||||
|
return "Tebi"
|
||||||
|
default:
|
||||||
|
return "Unkn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scale) Prefix() string {
|
||||||
|
switch *s {
|
||||||
|
case Base:
|
||||||
|
return ""
|
||||||
|
case Kilo:
|
||||||
|
return "K"
|
||||||
|
case Mega:
|
||||||
|
return "M"
|
||||||
|
case Giga:
|
||||||
|
return "G"
|
||||||
|
case Tera:
|
||||||
|
return "T"
|
||||||
|
case Peta:
|
||||||
|
return "P"
|
||||||
|
case Milli:
|
||||||
|
return "m"
|
||||||
|
case Micro:
|
||||||
|
return "u"
|
||||||
|
case Nano:
|
||||||
|
return "n"
|
||||||
|
case Kibi:
|
||||||
|
return "Ki"
|
||||||
|
case Mebi:
|
||||||
|
return "Mi"
|
||||||
|
case Gibi:
|
||||||
|
return "Gi"
|
||||||
|
case Tebi:
|
||||||
|
return "Ti"
|
||||||
|
default:
|
||||||
|
return "<unkn>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScale(prefix string) Scale {
|
||||||
|
switch prefix {
|
||||||
|
case "k":
|
||||||
|
return Kilo
|
||||||
|
case "K":
|
||||||
|
return Kilo
|
||||||
|
case "m":
|
||||||
|
return Milli
|
||||||
|
case "M":
|
||||||
|
return Mega
|
||||||
|
case "g":
|
||||||
|
return Giga
|
||||||
|
case "G":
|
||||||
|
return Giga
|
||||||
|
case "t":
|
||||||
|
return Tera
|
||||||
|
case "T":
|
||||||
|
return Tera
|
||||||
|
case "u":
|
||||||
|
return Micro
|
||||||
|
case "n":
|
||||||
|
return Nano
|
||||||
|
case "ki":
|
||||||
|
return Kibi
|
||||||
|
case "Ki":
|
||||||
|
return Kibi
|
||||||
|
case "Mi":
|
||||||
|
return Mebi
|
||||||
|
case "gi":
|
||||||
|
return Gibi
|
||||||
|
case "Gi":
|
||||||
|
return Gibi
|
||||||
|
case "Ti":
|
||||||
|
return Tebi
|
||||||
|
case "":
|
||||||
|
return Base
|
||||||
|
default:
|
||||||
|
return Base
|
||||||
|
}
|
||||||
|
}
|
75
internal/ccUnits/ccUnits.go
Normal file
75
internal/ccUnits/ccUnits.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package ccunits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Unit struct {
|
||||||
|
scale Scale
|
||||||
|
measure Measure
|
||||||
|
divMeasure Measure
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) String() string {
|
||||||
|
if u.divMeasure != None {
|
||||||
|
return fmt.Sprintf("%s%s/%s", u.scale.String(), u.measure.String(), u.divMeasure.String())
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%s%s", u.scale.String(), u.measure.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) Short() string {
|
||||||
|
if u.divMeasure != None {
|
||||||
|
return fmt.Sprintf("%s%s/%s", u.scale.Prefix(), u.measure.Short(), u.divMeasure.Short())
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%s%s", u.scale.Prefix(), u.measure.Short())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Unit) AddDivisorUnit(div Measure) {
|
||||||
|
u.divMeasure = div
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetScaleFactor(in Scale, out Scale) float64 {
|
||||||
|
var factor = 1.0
|
||||||
|
var in_scale = 1.0
|
||||||
|
var out_scale = 1.0
|
||||||
|
if in != Base {
|
||||||
|
in_scale = float64(in)
|
||||||
|
}
|
||||||
|
if out != Base {
|
||||||
|
out_scale = float64(out)
|
||||||
|
}
|
||||||
|
factor = in_scale / out_scale
|
||||||
|
return factor
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUnitScaleFactor(in Unit, out Unit) (float64, error) {
|
||||||
|
if in.measure != out.measure || in.divMeasure != out.divMeasure {
|
||||||
|
return 1.0, fmt.Errorf("invalid measures in in and out Unit")
|
||||||
|
}
|
||||||
|
return GetScaleFactor(in.scale, out.scale), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnit(unit string) Unit {
|
||||||
|
u := Unit{
|
||||||
|
scale: Base,
|
||||||
|
measure: None,
|
||||||
|
divMeasure: None,
|
||||||
|
}
|
||||||
|
matches := prefixRegex.FindStringSubmatch(unit)
|
||||||
|
if len(matches) > 2 {
|
||||||
|
u.scale = NewScale(matches[1])
|
||||||
|
measures := strings.Split(matches[2], "/")
|
||||||
|
u.measure = NewMeasure(measures[0])
|
||||||
|
// Special case for 'm' as scale for Bytes as thers is nothing like MilliBytes
|
||||||
|
if u.measure == Bytes && u.scale == Milli {
|
||||||
|
u.scale = Mega
|
||||||
|
}
|
||||||
|
if len(measures) > 1 {
|
||||||
|
u.divMeasure = NewMeasure(measures[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
90
internal/ccUnits/ccUnits_test.go
Normal file
90
internal/ccUnits/ccUnits_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package ccunits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnitsExact(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in string
|
||||||
|
want Unit
|
||||||
|
}{
|
||||||
|
{"b", NewUnit("Bytes")},
|
||||||
|
{"B", NewUnit("Bytes")},
|
||||||
|
{"byte", NewUnit("Bytes")},
|
||||||
|
{"bytes", NewUnit("Bytes")},
|
||||||
|
{"BYtes", NewUnit("Bytes")},
|
||||||
|
{"Mb", NewUnit("MBytes")},
|
||||||
|
{"MB", NewUnit("MBytes")},
|
||||||
|
{"Mbyte", NewUnit("MBytes")},
|
||||||
|
{"Mbytes", NewUnit("MBytes")},
|
||||||
|
{"MbYtes", NewUnit("MBytes")},
|
||||||
|
{"Gb", NewUnit("GBytes")},
|
||||||
|
{"GB", NewUnit("GBytes")},
|
||||||
|
{"Hz", NewUnit("Hertz")},
|
||||||
|
{"MHz", NewUnit("MHertz")},
|
||||||
|
{"GHertz", NewUnit("GHertz")},
|
||||||
|
{"pkts", NewUnit("Packets")},
|
||||||
|
{"packets", NewUnit("Packets")},
|
||||||
|
{"packet", NewUnit("Packets")},
|
||||||
|
{"flop", NewUnit("Flops")},
|
||||||
|
{"flops", NewUnit("Flops")},
|
||||||
|
{"floPS", NewUnit("Flops")},
|
||||||
|
{"Mflop", NewUnit("MFlops")},
|
||||||
|
{"Gflop", NewUnit("GFlops")},
|
||||||
|
{"gflop", NewUnit("GFlops")},
|
||||||
|
{"%", NewUnit("Percent")},
|
||||||
|
{"percent", NewUnit("Percent")},
|
||||||
|
{"degc", NewUnit("degC")},
|
||||||
|
{"degC", NewUnit("degC")},
|
||||||
|
{"degf", NewUnit("degF")},
|
||||||
|
{"°f", NewUnit("degF")},
|
||||||
|
}
|
||||||
|
compareUnitExact := func(in, out Unit) bool {
|
||||||
|
if in.measure == out.measure && in.divMeasure == out.divMeasure && in.scale == out.scale {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range testCases {
|
||||||
|
u := NewUnit(c.in)
|
||||||
|
if !compareUnitExact(u, c.want) {
|
||||||
|
t.Errorf("func NewUnit(%q) == %q, want %q", c.in, u.String(), c.want.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnitsDifferentScale(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
in string
|
||||||
|
want Unit
|
||||||
|
scaleFactor float64
|
||||||
|
}{
|
||||||
|
{"kb", NewUnit("Bytes"), 1000},
|
||||||
|
{"Mb", NewUnit("Bytes"), 1000000},
|
||||||
|
{"Mb/s", NewUnit("Bytes/s"), 1000000},
|
||||||
|
{"Flops/s", NewUnit("MFlops/s"), 1e-6},
|
||||||
|
{"Flops/s", NewUnit("GFlops/s"), 1e-9},
|
||||||
|
{"MHz", NewUnit("Hertz"), 1e6},
|
||||||
|
{"kb", NewUnit("Kib"), 1000.0 / 1024},
|
||||||
|
{"Mib", NewUnit("MBytes"), (1024 * 1024.0) / (1e6)},
|
||||||
|
{"mb", NewUnit("MBytes"), 1.0},
|
||||||
|
}
|
||||||
|
compareUnitWithScale := func(in, out Unit, factor float64) bool {
|
||||||
|
if in.measure == out.measure && in.divMeasure == out.divMeasure {
|
||||||
|
if f := GetScaleFactor(in.scale, out.scale); f == factor {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
fmt.Println(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range testCases {
|
||||||
|
u := NewUnit(c.in)
|
||||||
|
if !compareUnitWithScale(u, c.want, c.scaleFactor) {
|
||||||
|
t.Errorf("func NewUnit(%q) == %q, want %q with factor %f", c.in, u.String(), c.want.String(), c.scaleFactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user