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:
There are basically two important functions:
```go
NewUnit(unit string) Unit
GetUnitPrefixFactor(in Unit, out Unit) (func(value float64) float64, error) // Get conversion function for the value
NewUnit(unit string) Unit // create a new unit from some string like 'GHz', 'Mbyte' or 'kevents/s'
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 {
Valid() bool
@ -26,14 +28,35 @@ v := NewUnit("foo")
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
// Get conversion functions for 'kB' to 'MBytes'
u1 := NewUnit("kB")
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 {
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).

View File

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

View File

@ -5,20 +5,27 @@ import "regexp"
type Prefix float64
const (
Base Prefix = 1
Exa = 1e18
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
InvalidPrefix Prefix = iota
Base = 1
Yotta = 1e24
Zetta = 1e21
Exa = 1e18
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
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]?)(.*)`
@ -26,6 +33,8 @@ var prefixRegex = regexp.MustCompile(prefixRegexStr)
func (s *Prefix) String() string {
switch *s {
case InvalidPrefix:
return "Inval"
case Base:
return ""
case Kilo:
@ -40,6 +49,10 @@ func (s *Prefix) String() string {
return "Peta"
case Exa:
return "Exa"
case Zetta:
return "Zetta"
case Yotta:
return "Yotta"
case Milli:
return "Milli"
case Micro:
@ -61,6 +74,8 @@ func (s *Prefix) String() string {
func (s *Prefix) Prefix() string {
switch *s {
case InvalidPrefix:
return "<inval>"
case Base:
return ""
case Kilo:
@ -75,6 +90,10 @@ func (s *Prefix) Prefix() string {
return "P"
case Exa:
return "E"
case Zetta:
return "Z"
case Yotta:
return "Y"
case Milli:
return "m"
case Micro:
@ -120,6 +139,14 @@ func NewPrefix(prefix string) Prefix {
return Exa
case "E":
return Exa
case "z":
return Zetta
case "Z":
return Zetta
case "y":
return Yotta
case "Y":
return Yotta
case "u":
return Micro
case "n":
@ -139,6 +166,6 @@ func NewPrefix(prefix string) Prefix {
case "":
return Base
default:
return Base
return InvalidPrefix
}
}

View File

@ -19,14 +19,17 @@ type Unit interface {
getPrefix() Prefix
getMeasure() Measure
getDivMeasure() Measure
setPrefix(p Prefix)
}
var INVALID_UNIT = NewUnit("foobar")
func (u *unit) Valid() bool {
return u.measure != None
return u.measure != InvalidMeasure
}
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())
} else {
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 {
if u.divMeasure != None {
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())
@ -49,6 +52,10 @@ 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
}
@ -57,7 +64,7 @@ func (u *unit) getDivMeasure() Measure {
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 in_prefix = float64(in)
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 }
}
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 {
return func(value float64) float64 { return (value * 1.8) + 32 }, nil
} 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() {
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 {
u := &unit{
prefix: Base,
measure: None,
divMeasure: None,
prefix: InvalidPrefix,
measure: InvalidMeasure,
divMeasure: InvalidMeasure,
}
matches := prefixRegex.FindStringSubmatch(unitStr)
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'
// like 'packets' or 'percent'. Same for 'e' or 'E' (Exa) for measures starting with
// 'e' or 'E' like 'events'
if m == None {
if m == InvalidMeasure {
switch pre {
case Peta, Exa:
t := NewMeasure(matches[1] + measures[0])
if t != None {
if t != InvalidMeasure {
m = t
pre = Base
}
}
}
div := None
div := InvalidMeasure
if len(measures) > 1 {
div = NewMeasure(measures[1])
}
@ -115,9 +148,13 @@ func NewUnit(unitStr string) Unit {
case Percentage:
pre = Base
}
u.prefix = pre
u.measure = m
u.divMeasure = div
if pre != InvalidPrefix && m != InvalidMeasure {
u.prefix = pre
u.measure = m
if div != InvalidMeasure {
u.divMeasure = div
}
}
}
return u
}

View File

@ -24,7 +24,7 @@ func TestUnitsExact(t *testing.T) {
{"GB", NewUnit("GBytes")},
{"Hz", NewUnit("Hertz")},
{"MHz", NewUnit("MHertz")},
{"GHertz", NewUnit("GHertz")},
{"GHz", NewUnit("GHertz")},
{"pkts", NewUnit("Packets")},
{"packets", NewUnit("Packets")},
{"packet", NewUnit("Packets")},
@ -44,7 +44,7 @@ func TestUnitsExact(t *testing.T) {
{"event", NewUnit("events")},
{"EveNts", NewUnit("events")},
{"reqs", NewUnit("requests")},
{"requests", NewUnit("requests")},
{"reQuEsTs", NewUnit("requests")},
{"Requests", NewUnit("requests")},
{"cyc", 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 {
in string
want Unit
@ -97,7 +97,7 @@ func TestUnitsDifferentPrefix(t *testing.T) {
}
compareUnitWithPrefix := func(in, out Unit, factor float64) bool {
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
} else {
fmt.Println(f(1.0))
@ -108,7 +108,73 @@ func TestUnitsDifferentPrefix(t *testing.T) {
for _, c := range testCases {
u := NewUnit(c.in)
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)
}
}
}