mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-25 12:59:06 +01:00
340 lines
9.5 KiB
Go
340 lines
9.5 KiB
Go
// Unit system for cluster monitoring metrics like bytes, flops and events
|
|
package units
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
|
)
|
|
|
|
type unit struct {
|
|
prefix Prefix
|
|
measure Measure
|
|
divMeasure Measure
|
|
}
|
|
|
|
type Unit interface {
|
|
Valid() bool
|
|
String() string
|
|
Short() string
|
|
AddUnitDenominator(div Measure)
|
|
getPrefix() Prefix
|
|
getMeasure() Measure
|
|
getUnitDenominator() Measure
|
|
setPrefix(p Prefix)
|
|
}
|
|
|
|
var INVALID_UNIT = NewUnit("foobar")
|
|
|
|
// Valid checks whether a unit is a valid unit.
|
|
// A unit is valid if it has at least a prefix and a measure.
|
|
// The unit denominator is optional.
|
|
func (u *unit) Valid() bool {
|
|
return u.prefix != InvalidPrefix && u.measure != InvalidMeasure
|
|
}
|
|
|
|
// String returns the long string for the unit like 'KiloHertz' or 'MegaBytes'
|
|
func (u *unit) String() string {
|
|
if u.divMeasure != InvalidMeasure {
|
|
return fmt.Sprintf("%s%s/%s", u.prefix.String(), u.measure.String(), u.divMeasure.String())
|
|
} else {
|
|
return fmt.Sprintf("%s%s", u.prefix.String(), u.measure.String())
|
|
}
|
|
}
|
|
|
|
// Short returns the short string for the unit like 'kHz' or 'MByte'. Is is recommened to use Short() over String().
|
|
func (u *unit) Short() string {
|
|
if u.divMeasure != InvalidMeasure {
|
|
return fmt.Sprintf("%s%s/%s", u.prefix.Prefix(), u.measure.Short(), u.divMeasure.Short())
|
|
} else {
|
|
return fmt.Sprintf("%s%s", u.prefix.Prefix(), u.measure.Short())
|
|
}
|
|
}
|
|
|
|
// AddUnitDenominator adds a unit denominator to an exising unit. Can be used if you want to derive e.g. data volume to bandwidths.
|
|
// The data volume is in a Byte unit like 'kByte' and by dividing it by the runtime in seconds, we get the bandwidth. We can use the
|
|
// data volume unit and add 'Second' as unit denominator
|
|
func (u *unit) AddUnitDenominator(div Measure) {
|
|
u.divMeasure = div
|
|
}
|
|
|
|
func (u *unit) getPrefix() Prefix {
|
|
return u.prefix
|
|
}
|
|
|
|
func (u *unit) setPrefix(p Prefix) {
|
|
u.prefix = p
|
|
}
|
|
|
|
func (u *unit) getMeasure() Measure {
|
|
return u.measure
|
|
}
|
|
|
|
func (u *unit) getUnitDenominator() Measure {
|
|
return u.divMeasure
|
|
}
|
|
|
|
func ConvertValue(v *float64, from string, to string) {
|
|
uf := NewUnit(from)
|
|
ut := NewUnit(to)
|
|
factor := float64(uf.getPrefix()) / float64(ut.getPrefix())
|
|
*v = math.Ceil(*v * factor)
|
|
}
|
|
|
|
func ConvertSeries(s []float64, from string, to string) {
|
|
uf := NewUnit(from)
|
|
ut := NewUnit(to)
|
|
factor := float64(uf.getPrefix()) / float64(ut.getPrefix())
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
s[i] = math.Ceil(s[i] * factor)
|
|
}
|
|
}
|
|
|
|
func getNormalizationFactor(v float64) (float64, int) {
|
|
count := 0
|
|
scale := -3
|
|
|
|
if v > 1000.0 {
|
|
for v > 1000.0 {
|
|
v *= 1e-3
|
|
count++
|
|
}
|
|
} else {
|
|
for v < 1.0 {
|
|
v *= 1e3
|
|
count++
|
|
}
|
|
scale = 3
|
|
}
|
|
return math.Pow10(count * scale), count * scale
|
|
}
|
|
|
|
func NormalizeValue(v *float64, us string) string {
|
|
u := NewUnit(us)
|
|
f, e := getNormalizationFactor((*v))
|
|
*v = math.Ceil(*v * f)
|
|
p := NewPrefixFromFactor(u.getPrefix(), e)
|
|
return p.Prefix()
|
|
}
|
|
|
|
func NormalizeSeries(s []float64, avg float64, us string, nu *string) {
|
|
u := NewUnit(us)
|
|
f, e := getNormalizationFactor(avg)
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
s[i] *= f
|
|
s[i] = math.Ceil(s[i])
|
|
}
|
|
u.setPrefix(NewPrefixFromFactor(u.getPrefix(), e))
|
|
fmt.Printf("Prefix: %e \n", u.getPrefix())
|
|
*nu = u.Short()
|
|
}
|
|
|
|
func ConvertUnitString(us string) schema.Unit {
|
|
var nu schema.Unit
|
|
|
|
if us == "CPI" ||
|
|
us == "IPC" ||
|
|
us == "load" ||
|
|
us == "" {
|
|
nu.Base = us
|
|
return nu
|
|
}
|
|
u := NewUnit(us)
|
|
p := u.getPrefix()
|
|
if p.Prefix() != "" {
|
|
prefix := p.Prefix()
|
|
nu.Prefix = prefix
|
|
}
|
|
m := u.getMeasure()
|
|
d := u.getUnitDenominator()
|
|
if d.Short() != "inval" {
|
|
nu.Base = fmt.Sprintf("%s/%s", m.Short(), d.Short())
|
|
} else {
|
|
nu.Base = m.Short()
|
|
}
|
|
|
|
return nu
|
|
}
|
|
|
|
// GetPrefixPrefixFactor creates the default conversion function between two prefixes.
|
|
// It returns a conversation function for the value.
|
|
func GetPrefixPrefixFactor(in Prefix, out Prefix) func(value interface{}) interface{} {
|
|
var factor = 1.0
|
|
var in_prefix = float64(in)
|
|
var out_prefix = float64(out)
|
|
factor = in_prefix / out_prefix
|
|
conv := func(value interface{}) interface{} {
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return v * factor
|
|
case float32:
|
|
return float32(float64(v) * factor)
|
|
case int:
|
|
return int(float64(v) * factor)
|
|
case int32:
|
|
return int32(float64(v) * factor)
|
|
case int64:
|
|
return int64(float64(v) * factor)
|
|
case uint:
|
|
return uint(float64(v) * factor)
|
|
case uint32:
|
|
return uint32(float64(v) * factor)
|
|
case uint64:
|
|
return uint64(float64(v) * factor)
|
|
}
|
|
return value
|
|
}
|
|
return conv
|
|
}
|
|
|
|
// This is the conversion function between temperatures in Celsius to Fahrenheit
|
|
func convertTempC2TempF(value interface{}) interface{} {
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return (v * 1.8) + 32
|
|
case float32:
|
|
return (v * 1.8) + 32
|
|
case int:
|
|
return int((float64(v) * 1.8) + 32)
|
|
case int32:
|
|
return int32((float64(v) * 1.8) + 32)
|
|
case int64:
|
|
return int64((float64(v) * 1.8) + 32)
|
|
case uint:
|
|
return uint((float64(v) * 1.8) + 32)
|
|
case uint32:
|
|
return uint32((float64(v) * 1.8) + 32)
|
|
case uint64:
|
|
return uint64((float64(v) * 1.8) + 32)
|
|
}
|
|
return value
|
|
}
|
|
|
|
// This is the conversion function between temperatures in Fahrenheit to Celsius
|
|
func convertTempF2TempC(value interface{}) interface{} {
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return (v - 32) / 1.8
|
|
case float32:
|
|
return (v - 32) / 1.8
|
|
case int:
|
|
return int(((float64(v) - 32) / 1.8))
|
|
case int32:
|
|
return int32(((float64(v) - 32) / 1.8))
|
|
case int64:
|
|
return int64(((float64(v) - 32) / 1.8))
|
|
case uint:
|
|
return uint(((float64(v) - 32) / 1.8))
|
|
case uint32:
|
|
return uint32(((float64(v) - 32) / 1.8))
|
|
case uint64:
|
|
return uint64(((float64(v) - 32) / 1.8))
|
|
}
|
|
return value
|
|
}
|
|
|
|
// GetPrefixStringPrefixStringFactor is a wrapper for GetPrefixPrefixFactor with string inputs instead
|
|
// of prefixes. It also returns a conversation function for the value.
|
|
func GetPrefixStringPrefixStringFactor(in string, out string) func(value interface{}) interface{} {
|
|
var i Prefix = NewPrefix(in)
|
|
var o Prefix = NewPrefix(out)
|
|
return GetPrefixPrefixFactor(i, o)
|
|
}
|
|
|
|
// GetUnitPrefixFactor gets the conversion function and resulting unit for a unit and a prefix. This is
|
|
// the most common case where you have some input unit and want to convert it to the same unit but with
|
|
// a different prefix. The returned unit represents the value after conversation.
|
|
func GetUnitPrefixFactor(in Unit, out Prefix) (func(value interface{}) interface{}, Unit) {
|
|
outUnit := NewUnit(in.Short())
|
|
if outUnit.Valid() {
|
|
outUnit.setPrefix(out)
|
|
conv := GetPrefixPrefixFactor(in.getPrefix(), out)
|
|
return conv, outUnit
|
|
}
|
|
return nil, INVALID_UNIT
|
|
}
|
|
|
|
// GetUnitPrefixStringFactor gets the conversion function and resulting unit for a unit and a prefix as string.
|
|
// It is a wrapper for GetUnitPrefixFactor
|
|
func GetUnitPrefixStringFactor(in Unit, out string) (func(value interface{}) interface{}, Unit) {
|
|
var o Prefix = NewPrefix(out)
|
|
return GetUnitPrefixFactor(in, o)
|
|
}
|
|
|
|
// GetUnitStringPrefixStringFactor gets the conversion function and resulting unit for a unit and a prefix when both are only string representations.
|
|
// This is just a wrapper for GetUnitPrefixFactor with the given input unit and the desired output prefix.
|
|
func GetUnitStringPrefixStringFactor(in string, out string) (func(value interface{}) interface{}, Unit) {
|
|
var i = NewUnit(in)
|
|
return GetUnitPrefixStringFactor(i, out)
|
|
}
|
|
|
|
// GetUnitUnitFactor gets the conversion function and (maybe) error for unit to unit conversion.
|
|
// It is basically a wrapper for GetPrefixPrefixFactor with some special cases for temperature
|
|
// conversion between Fahrenheit and Celsius.
|
|
func GetUnitUnitFactor(in Unit, out Unit) (func(value interface{}) interface{}, error) {
|
|
if in.getMeasure() == TemperatureC && out.getMeasure() == TemperatureF {
|
|
return convertTempC2TempF, nil
|
|
} else if in.getMeasure() == TemperatureF && out.getMeasure() == TemperatureC {
|
|
return convertTempF2TempC, nil
|
|
} else if in.getMeasure() != out.getMeasure() || in.getUnitDenominator() != out.getUnitDenominator() {
|
|
return func(value interface{}) interface{} { return 1.0 }, fmt.Errorf("UNITS/UNITS > invalid measures in in and out Unit")
|
|
}
|
|
return GetPrefixPrefixFactor(in.getPrefix(), out.getPrefix()), nil
|
|
}
|
|
|
|
// NewUnit creates a new unit out of a string representing a unit like 'Mbyte/s' or 'GHz'.
|
|
// It uses regular expressions to detect the prefix, unit and (maybe) unit denominator.
|
|
func NewUnit(unitStr string) Unit {
|
|
u := &unit{
|
|
prefix: InvalidPrefix,
|
|
measure: InvalidMeasure,
|
|
divMeasure: InvalidMeasure,
|
|
}
|
|
matches := prefixUnitSplitRegex.FindStringSubmatch(unitStr)
|
|
if len(matches) > 2 {
|
|
pre := NewPrefix(matches[1])
|
|
measures := strings.Split(matches[2], "/")
|
|
m := NewMeasure(measures[0])
|
|
// Special case for prefix 'p' or 'P' (Peta) and measures starting with 'p' or 'P'
|
|
// like 'packets' or 'percent'. Same for 'e' or 'E' (Exa) for measures starting with
|
|
// 'e' or 'E' like 'events'
|
|
if m == InvalidMeasure {
|
|
switch pre {
|
|
case Peta, Exa:
|
|
t := NewMeasure(matches[1] + measures[0])
|
|
if t != InvalidMeasure {
|
|
m = t
|
|
pre = Base
|
|
}
|
|
}
|
|
}
|
|
div := InvalidMeasure
|
|
if len(measures) > 1 {
|
|
div = NewMeasure(measures[1])
|
|
}
|
|
|
|
switch m {
|
|
// Special case for 'm' as prefix for Bytes and some others as thers is no unit like MilliBytes
|
|
case Bytes, Flops, Packets, Events, Cycles, Requests:
|
|
if pre == Milli {
|
|
pre = Mega
|
|
}
|
|
// Special case for percentage. No/ignore prefix
|
|
case Percentage:
|
|
pre = Base
|
|
}
|
|
if pre != InvalidPrefix && m != InvalidMeasure {
|
|
u.prefix = pre
|
|
u.measure = m
|
|
if div != InvalidMeasure {
|
|
u.divMeasure = div
|
|
}
|
|
}
|
|
}
|
|
return u
|
|
}
|