mirror of
https://github.com/ClusterCockpit/cc-units.git
synced 2024-12-25 13:09:06 +01:00
Add new prefixes, add some more interface functions
This commit is contained in:
parent
8c6fa4c4bc
commit
a6e40789e4
31
README.md
31
README.md
@ -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).
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
67
ccUnits.go
67
ccUnits.go
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user