Add new prefixes, add some more interface functions

This commit is contained in:
Thomas Roehl 2022-03-17 14:18:07 +01:00
parent 8c6fa4c4bc
commit a6e40789e4
5 changed files with 200 additions and 43 deletions

View File

@ -5,8 +5,10 @@ When working with metrics, the problem comes up that they may use different unit
In order to enable unit comparison and conversion, the ccUnits package provides some helpers: In order to enable unit comparison and conversion, the ccUnits package provides some helpers:
There are basically two important functions: There are basically two important functions:
```go ```go
NewUnit(unit string) Unit NewUnit(unit string) Unit // create a new unit from some string like 'GHz', 'Mbyte' or 'kevents/s'
GetUnitPrefixFactor(in Unit, out Unit) (func(value float64) float64, error) // Get conversion function for the value func GetUnitUnitFactor(in Unit, out Unit) (func(value float64) float64, error) // Get conversion function between two units
func GetPrefixFactor(in Prefix, out Prefix) func(value float64) float64 // Get conversion function between two prefixes
func GetUnitPrefixFactor(in Unit, out Prefix) (func(value float64) float64, Unit) // Get conversion function for prefix changes and the new unit for further use
type Unit interface { type Unit interface {
Valid() bool Valid() bool
@ -26,14 +28,35 @@ v := NewUnit("foo")
fmt.Println(v.Valid()) // false fmt.Println(v.Valid()) // false
``` ```
If you have two units and need the conversion function: If you have two units or other components and need the conversion function:
```go ```go
// Get conversion functions for 'kB' to 'MBytes'
u1 := NewUnit("kB") u1 := NewUnit("kB")
u2 := NewUnit("MBytes") u2 := NewUnit("MBytes")
convFunc, err := GetUnitPrefixFactor(u1, u2) // Returns an error if the units have different measures convFunc, err := GetUnitUnitFactor(u1, u2) // Returns an error if the units have different measures
if err == nil { if err == nil {
v2 := convFunc(v1) v2 := convFunc(v1)
fmt.Printf("%f %s\n", v2, u2.Short())
} }
// Get conversion function for 'kB' -> 'G' prefix.
// Returns the function and the new unit 'GBytes'
p1 := NewPrefix("G")
convFunc, u_p1 := GetUnitPrefixFactor(u1, p1)
// or
// convFunc, u_p1 := GetUnitPrefixStringFactor(u1, "G")
if convFunc != nil {
v2 := convFunc(v1)
fmt.Printf("%f %s\n", v2, u_p1.Short())
}
// Get conversion function for two prefixes: 'G' -> 'T'
p2 := NewPrefix("T")
convFunc = GetPrefixPrefixFactor(p1, p2)
if convFunc != nil {
v2 := convFunc(v1)
fmt.Printf("%f %s -> %f %s\n", v1, p1.Prefix(), v2, p2.Prefix())
}
``` ```
(In the ClusterCockpit ecosystem the separation between values and units if useful since they are commonly not stored as a single entity but the value is a field in the CCMetric while unit is a tag or a meta information). (In the ClusterCockpit ecosystem the separation between values and units if useful since they are commonly not stored as a single entity but the value is a field in the CCMetric while unit is a tag or a meta information).

View File

@ -5,7 +5,7 @@ import "regexp"
type Measure int type Measure int
const ( const (
None Measure = iota InvalidMeasure Measure = iota
Bytes Bytes
Flops Flops
Percentage Percentage
@ -25,7 +25,7 @@ const (
func (m *Measure) String() string { func (m *Measure) String() string {
switch *m { switch *m {
case Bytes: case Bytes:
return "Bytes" return "byte"
case Flops: case Flops:
return "Flops" return "Flops"
case Percentage: case Percentage:
@ -52,6 +52,8 @@ func (m *Measure) String() string {
return "Packets" return "Packets"
case Events: case Events:
return "Events" return "Events"
case InvalidMeasure:
return "Invalid"
default: default:
return "Unknown" return "Unknown"
} }
@ -60,7 +62,7 @@ func (m *Measure) String() string {
func (m *Measure) Short() string { func (m *Measure) Short() string {
switch *m { switch *m {
case Bytes: case Bytes:
return "Bytes" return "B"
case Flops: case Flops:
return "Flops" return "Flops"
case Percentage: case Percentage:
@ -87,6 +89,8 @@ func (m *Measure) Short() string {
return "packets" return "packets"
case Events: case Events:
return "events" return "events"
case InvalidMeasure:
return "Invalid"
default: default:
return "Unknown" return "Unknown"
} }
@ -180,5 +184,5 @@ func NewMeasure(unit string) Measure {
if match != nil { if match != nil {
return Events return Events
} }
return None return InvalidMeasure
} }

View File

@ -5,7 +5,10 @@ import "regexp"
type Prefix float64 type Prefix float64
const ( const (
Base Prefix = 1 InvalidPrefix Prefix = iota
Base = 1
Yotta = 1e24
Zetta = 1e21
Exa = 1e18 Exa = 1e18
Peta = 1e15 Peta = 1e15
Tera = 1e12 Tera = 1e12
@ -19,6 +22,10 @@ const (
Mebi = 1024 * 1024 Mebi = 1024 * 1024
Gibi = 1024 * 1024 * 1024 Gibi = 1024 * 1024 * 1024
Tebi = 1024 * 1024 * 1024 * 1024 Tebi = 1024 * 1024 * 1024 * 1024
Pebi = 1024 * 1024 * 1024 * 1024 * 1024
Exbi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024
Zebi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
Yobi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
) )
const prefixRegexStr = `^([kKmMgGtTpP]?[i]?)(.*)` const prefixRegexStr = `^([kKmMgGtTpP]?[i]?)(.*)`
@ -26,6 +33,8 @@ var prefixRegex = regexp.MustCompile(prefixRegexStr)
func (s *Prefix) String() string { func (s *Prefix) String() string {
switch *s { switch *s {
case InvalidPrefix:
return "Inval"
case Base: case Base:
return "" return ""
case Kilo: case Kilo:
@ -40,6 +49,10 @@ func (s *Prefix) String() string {
return "Peta" return "Peta"
case Exa: case Exa:
return "Exa" return "Exa"
case Zetta:
return "Zetta"
case Yotta:
return "Yotta"
case Milli: case Milli:
return "Milli" return "Milli"
case Micro: case Micro:
@ -61,6 +74,8 @@ func (s *Prefix) String() string {
func (s *Prefix) Prefix() string { func (s *Prefix) Prefix() string {
switch *s { switch *s {
case InvalidPrefix:
return "<inval>"
case Base: case Base:
return "" return ""
case Kilo: case Kilo:
@ -75,6 +90,10 @@ func (s *Prefix) Prefix() string {
return "P" return "P"
case Exa: case Exa:
return "E" return "E"
case Zetta:
return "Z"
case Yotta:
return "Y"
case Milli: case Milli:
return "m" return "m"
case Micro: case Micro:
@ -120,6 +139,14 @@ func NewPrefix(prefix string) Prefix {
return Exa return Exa
case "E": case "E":
return Exa return Exa
case "z":
return Zetta
case "Z":
return Zetta
case "y":
return Yotta
case "Y":
return Yotta
case "u": case "u":
return Micro return Micro
case "n": case "n":
@ -139,6 +166,6 @@ func NewPrefix(prefix string) Prefix {
case "": case "":
return Base return Base
default: default:
return Base return InvalidPrefix
} }
} }

View File

@ -19,14 +19,17 @@ type Unit interface {
getPrefix() Prefix getPrefix() Prefix
getMeasure() Measure getMeasure() Measure
getDivMeasure() Measure getDivMeasure() Measure
setPrefix(p Prefix)
} }
var INVALID_UNIT = NewUnit("foobar")
func (u *unit) Valid() bool { func (u *unit) Valid() bool {
return u.measure != None return u.measure != InvalidMeasure
} }
func (u *unit) String() string { func (u *unit) String() string {
if u.divMeasure != None { if u.divMeasure != InvalidMeasure {
return fmt.Sprintf("%s%s/%s", u.prefix.String(), u.measure.String(), u.divMeasure.String()) return fmt.Sprintf("%s%s/%s", u.prefix.String(), u.measure.String(), u.divMeasure.String())
} else { } else {
return fmt.Sprintf("%s%s", u.prefix.String(), u.measure.String()) return fmt.Sprintf("%s%s", u.prefix.String(), u.measure.String())
@ -34,7 +37,7 @@ func (u *unit) String() string {
} }
func (u *unit) Short() string { func (u *unit) Short() string {
if u.divMeasure != None { if u.divMeasure != InvalidMeasure {
return fmt.Sprintf("%s%s/%s", u.prefix.Prefix(), u.measure.Short(), u.divMeasure.Short()) return fmt.Sprintf("%s%s/%s", u.prefix.Prefix(), u.measure.Short(), u.divMeasure.Short())
} else { } else {
return fmt.Sprintf("%s%s", u.prefix.Prefix(), u.measure.Short()) return fmt.Sprintf("%s%s", u.prefix.Prefix(), u.measure.Short())
@ -49,6 +52,10 @@ func (u *unit) getPrefix() Prefix {
return u.prefix return u.prefix
} }
func (u *unit) setPrefix(p Prefix) {
u.prefix = p
}
func (u *unit) getMeasure() Measure { func (u *unit) getMeasure() Measure {
return u.measure return u.measure
} }
@ -57,7 +64,7 @@ func (u *unit) getDivMeasure() Measure {
return u.divMeasure return u.divMeasure
} }
func GetPrefixFactor(in Prefix, out Prefix) func(value float64) float64 { func GetPrefixPrefixFactor(in Prefix, out Prefix) func(value float64) float64 {
var factor = 1.0 var factor = 1.0
var in_prefix = float64(in) var in_prefix = float64(in)
var out_prefix = float64(out) var out_prefix = float64(out)
@ -65,7 +72,33 @@ func GetPrefixFactor(in Prefix, out Prefix) func(value float64) float64 {
return func(value float64) float64 { return factor } return func(value float64) float64 { return factor }
} }
func GetUnitPrefixFactor(in Unit, out Unit) (func(value float64) float64, error) { func GetPrefixStringPrefixStringFactor(in string, out string) func(value float64) float64 {
var i Prefix = NewPrefix(in)
var o Prefix = NewPrefix(out)
return GetPrefixPrefixFactor(i, o)
}
func GetUnitPrefixFactor(in Unit, out Prefix) (func(value float64) float64, Unit) {
outUnit := NewUnit(in.Short())
if outUnit.Valid() {
outUnit.setPrefix(out)
conv := GetPrefixPrefixFactor(in.getPrefix(), out)
return conv, outUnit
}
return nil, INVALID_UNIT
}
func GetUnitPrefixStringFactor(in Unit, out string) (func(value float64) float64, Unit) {
var o Prefix = NewPrefix(out)
return GetUnitPrefixFactor(in, o)
}
func GetUnitStringPrefixStringFactor(in string, out string) (func(value float64) float64, Unit) {
var i = NewUnit(in)
return GetUnitPrefixStringFactor(i, out)
}
func GetUnitUnitFactor(in Unit, out Unit) (func(value float64) float64, error) {
if in.getMeasure() == TemperatureC && out.getMeasure() == TemperatureF { if in.getMeasure() == TemperatureC && out.getMeasure() == TemperatureF {
return func(value float64) float64 { return (value * 1.8) + 32 }, nil return func(value float64) float64 { return (value * 1.8) + 32 }, nil
} else if in.getMeasure() == TemperatureF && out.getMeasure() == TemperatureC { } else if in.getMeasure() == TemperatureF && out.getMeasure() == TemperatureC {
@ -73,14 +106,14 @@ func GetUnitPrefixFactor(in Unit, out Unit) (func(value float64) float64, error)
} else if in.getMeasure() != out.getMeasure() || in.getDivMeasure() != out.getDivMeasure() { } else if in.getMeasure() != out.getMeasure() || in.getDivMeasure() != out.getDivMeasure() {
return func(value float64) float64 { return 1.0 }, fmt.Errorf("invalid measures in in and out Unit") return func(value float64) float64 { return 1.0 }, fmt.Errorf("invalid measures in in and out Unit")
} }
return GetPrefixFactor(in.getPrefix(), out.getPrefix()), nil return GetPrefixPrefixFactor(in.getPrefix(), out.getPrefix()), nil
} }
func NewUnit(unitStr string) Unit { func NewUnit(unitStr string) Unit {
u := &unit{ u := &unit{
prefix: Base, prefix: InvalidPrefix,
measure: None, measure: InvalidMeasure,
divMeasure: None, divMeasure: InvalidMeasure,
} }
matches := prefixRegex.FindStringSubmatch(unitStr) matches := prefixRegex.FindStringSubmatch(unitStr)
if len(matches) > 2 { if len(matches) > 2 {
@ -90,17 +123,17 @@ func NewUnit(unitStr string) Unit {
// Special case for prefix 'p' or 'P' (Peta) and measures starting with 'p' or 'P' // 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 // like 'packets' or 'percent'. Same for 'e' or 'E' (Exa) for measures starting with
// 'e' or 'E' like 'events' // 'e' or 'E' like 'events'
if m == None { if m == InvalidMeasure {
switch pre { switch pre {
case Peta, Exa: case Peta, Exa:
t := NewMeasure(matches[1] + measures[0]) t := NewMeasure(matches[1] + measures[0])
if t != None { if t != InvalidMeasure {
m = t m = t
pre = Base pre = Base
} }
} }
} }
div := None div := InvalidMeasure
if len(measures) > 1 { if len(measures) > 1 {
div = NewMeasure(measures[1]) div = NewMeasure(measures[1])
} }
@ -115,9 +148,13 @@ func NewUnit(unitStr string) Unit {
case Percentage: case Percentage:
pre = Base pre = Base
} }
if pre != InvalidPrefix && m != InvalidMeasure {
u.prefix = pre u.prefix = pre
u.measure = m u.measure = m
if div != InvalidMeasure {
u.divMeasure = div u.divMeasure = div
} }
}
}
return u return u
} }

View File

@ -24,7 +24,7 @@ func TestUnitsExact(t *testing.T) {
{"GB", NewUnit("GBytes")}, {"GB", NewUnit("GBytes")},
{"Hz", NewUnit("Hertz")}, {"Hz", NewUnit("Hertz")},
{"MHz", NewUnit("MHertz")}, {"MHz", NewUnit("MHertz")},
{"GHertz", NewUnit("GHertz")}, {"GHz", NewUnit("GHertz")},
{"pkts", NewUnit("Packets")}, {"pkts", NewUnit("Packets")},
{"packets", NewUnit("Packets")}, {"packets", NewUnit("Packets")},
{"packet", NewUnit("Packets")}, {"packet", NewUnit("Packets")},
@ -44,7 +44,7 @@ func TestUnitsExact(t *testing.T) {
{"event", NewUnit("events")}, {"event", NewUnit("events")},
{"EveNts", NewUnit("events")}, {"EveNts", NewUnit("events")},
{"reqs", NewUnit("requests")}, {"reqs", NewUnit("requests")},
{"requests", NewUnit("requests")}, {"reQuEsTs", NewUnit("requests")},
{"Requests", NewUnit("requests")}, {"Requests", NewUnit("requests")},
{"cyc", NewUnit("cycles")}, {"cyc", NewUnit("cycles")},
{"cy", NewUnit("cycles")}, {"cy", NewUnit("cycles")},
@ -79,7 +79,7 @@ func TestUnitsExact(t *testing.T) {
} }
} }
func TestUnitsDifferentPrefix(t *testing.T) { func TestUnitUnitConversion(t *testing.T) {
testCases := []struct { testCases := []struct {
in string in string
want Unit want Unit
@ -97,7 +97,7 @@ func TestUnitsDifferentPrefix(t *testing.T) {
} }
compareUnitWithPrefix := func(in, out Unit, factor float64) bool { compareUnitWithPrefix := func(in, out Unit, factor float64) bool {
if in.getMeasure() == out.getMeasure() && in.getDivMeasure() == out.getDivMeasure() { if in.getMeasure() == out.getMeasure() && in.getDivMeasure() == out.getDivMeasure() {
if f := GetPrefixFactor(in.getPrefix(), out.getPrefix()); f(1.0) == factor { if f := GetPrefixPrefixFactor(in.getPrefix(), out.getPrefix()); f(1.0) == factor {
return true return true
} else { } else {
fmt.Println(f(1.0)) fmt.Println(f(1.0))
@ -108,7 +108,73 @@ func TestUnitsDifferentPrefix(t *testing.T) {
for _, c := range testCases { for _, c := range testCases {
u := NewUnit(c.in) u := NewUnit(c.in)
if (!u.Valid()) || (!compareUnitWithPrefix(u, c.want, c.prefixFactor)) { if (!u.Valid()) || (!compareUnitWithPrefix(u, c.want, c.prefixFactor)) {
t.Errorf("func NewUnit(%q) == %q, want %q with factor %f", c.in, u.String(), c.want.String(), c.prefixFactor) t.Errorf("GetPrefixPrefixFactor(%q, %q) invalid, want %q with factor %f", c.in, u.String(), c.want.String(), c.prefixFactor)
}
}
}
func TestUnitPrefixConversion(t *testing.T) {
testCases := []struct {
in string
want string
prefixFactor float64
wantUnit Unit
}{
{"KBytes", "", 1000, NewUnit("Bytes")},
{"MBytes", "", 1e6, NewUnit("Bytes")},
{"MBytes", "G", 1e-3, NewUnit("GBytes")},
{"mb", "M", 1, NewUnit("MBytes")},
}
compareUnitPrefix := func(in Unit, out Prefix, factor float64, outUnit Unit) bool {
if in.Valid() {
conv, unit := GetUnitPrefixFactor(in, out)
value := conv(1.0)
if value == factor && unit.String() == outUnit.String() {
return true
}
}
return false
}
for _, c := range testCases {
u := NewUnit(c.in)
p := NewPrefix(c.want)
if (!u.Valid()) || (!compareUnitPrefix(u, p, c.prefixFactor, c.wantUnit)) {
t.Errorf("GetUnitPrefixFactor(%q, %q) invalid, want %q with factor %f", c.in, p.Prefix(), c.wantUnit.String(), c.prefixFactor)
}
}
}
func TestPrefixPrefixConversion(t *testing.T) {
testCases := []struct {
in string
want string
prefixFactor float64
}{
{"K", "", 1000},
{"M", "", 1e6},
{"M", "G", 1e-3},
{"", "M", 1e-6},
{"", "m", 1e3},
{"m", "n", 1e6},
//{"", "n", 1e9} does not work because of IEEE rounding problems
}
comparePrefixPrefix := func(in Prefix, out Prefix, factor float64) bool {
if in != InvalidPrefix && out != InvalidPrefix {
conv := GetPrefixPrefixFactor(in, out)
value := conv(1.0)
fmt.Println("1.0 -> ", value, ", want ", factor)
if value == factor {
return true
}
}
return false
}
for _, c := range testCases {
i := NewPrefix(c.in)
o := NewPrefix(c.want)
if !comparePrefixPrefix(i, o, c.prefixFactor) {
t.Errorf("GetPrefixPrefixFactor(%q, %q) invalid, want %q with factor %f", c.in, c.want, o.Prefix(), c.prefixFactor)
} }
} }
} }