diff --git a/README.md b/README.md index fd916fc..b5ac5ac 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ -# ccUnits - A unit system for ClusterCockpit +# cc-units - A unit system for ClusterCockpit -When working with metrics, the problem comes up that they may use different unit name but have the same unit in fact. There are a lot of real world examples like 'kB' and 'Kbyte'. In CC Metric Collector, the Collectors read data from different sources which may use different units or the programmer specifies a unit for a metric by hand. The ccUnits system is not comparable with the SI unit system. If you are looking for a package for the SI units, see [here](https://pkg.go.dev/github.com/gurre/si). +When working with metrics, the problem comes up that they may use different unit name but have the same unit in fact. There are a lot of real world examples like 'kB' and 'Kbyte'. In [cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector), the collectors read data from different sources which may use different units or the programmer specifies a unit for a metric by hand. The cc-units system is not comparable with the SI unit system. If you are looking for a package for the SI units, see [here](https://pkg.go.dev/github.com/gurre/si). 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 // 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 diff --git a/ccUnitMeasure.go b/ccUnitMeasure.go index 5c57e65..dd1450a 100644 --- a/ccUnitMeasure.go +++ b/ccUnitMeasure.go @@ -12,7 +12,7 @@ const ( TemperatureC TemperatureF Rotation - Hertz + Frequency Time Watt Joule @@ -22,171 +22,113 @@ const ( Events ) +type MeasureData struct { + Long string + Short string + Regex string +} + +// Different names and regex used for input and output +var InvalidMeasureLong string = "Invalid" +var InvalidMeasureShort string = "inval" +var MeasuresMap map[Measure]MeasureData = map[Measure]MeasureData{ + Bytes: { + Long: "byte", + Short: "B", + Regex: "^([bB][yY]?[tT]?[eE]?[sS]?)", + }, + Flops: { + Long: "Flops", + Short: "Flops", + Regex: "^([fF][lL]?[oO]?[pP]?[sS]?)", + }, + Percentage: { + Long: "Percent", + Short: "%", + Regex: "^(%|[pP]ercent)", + }, + TemperatureC: { + Long: "DegreeC", + Short: "degC", + Regex: "^(deg[Cc]|°[cC])", + }, + TemperatureF: { + Long: "DegreeF", + Short: "degF", + Regex: "^(deg[fF]|°[fF])", + }, + Rotation: { + Long: "RPM", + Short: "RPM", + Regex: "^([rR][pP][mM])", + }, + Frequency: { + Long: "Hertz", + Short: "Hz", + Regex: "^([hH][eE]?[rR]?[tT]?[zZ])", + }, + Time: { + Long: "Seconds", + Short: "s", + Regex: "^([sS][eE]?[cC]?[oO]?[nN]?[dD]?[sS]?)", + }, + Cycles: { + Long: "Cycles", + Short: "cyc", + Regex: "^([cC][yY][cC]?[lL]?[eE]?[sS]?)", + }, + Watt: { + Long: "Watts", + Short: "W", + Regex: "^([wW][aA]?[tT]?[tT]?[sS]?)", + }, + Joule: { + Long: "Joules", + Short: "J", + Regex: "^([jJ][oO]?[uU]?[lL]?[eE]?[sS]?)", + }, + Requests: { + Long: "Requests", + Short: "requests", + Regex: "^([rR][eE][qQ][uU]?[eE]?[sS]?[tT]?[sS]?)", + }, + Packets: { + Long: "Packets", + Short: "packets", + Regex: "^([pP][aA]?[cC]?[kK][eE]?[tT][sS]?)", + }, + Events: { + Long: "Events", + Short: "events", + Regex: "^([eE][vV]?[eE]?[nN][tT][sS]?)", + }, +} + // String returns the long string for the measure like 'Percent' or 'Seconds' func (m *Measure) String() string { - switch *m { - case Bytes: - return "byte" - 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 Watt: - return "Watts" - case Joule: - return "Joules" - case Cycles: - return "Cycles" - case Requests: - return "Requests" - case Packets: - return "Packets" - case Events: - return "Events" - case InvalidMeasure: - return "Invalid" - default: - return "Unknown" + if data, ok := MeasuresMap[*m]; ok { + return data.Long } + return InvalidMeasureLong } // Short returns the short string for the measure like 'B' (Bytes), 's' (Time) or 'W' (Watt). Is is recommened to use Short() over String(). func (m *Measure) Short() string { - switch *m { - case Bytes: - return "B" - 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 Watt: - return "W" - case Joule: - return "J" - case Cycles: - return "cyc" - case Requests: - return "requests" - case Packets: - return "packets" - case Events: - return "events" - case InvalidMeasure: - return "Invalid" - default: - return "Unknown" + if data, ok := MeasuresMap[*m]; ok { + return data.Short } + return InvalidMeasureShort } -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 wattRegexStr = `^([wW][aA]?[tT]?[tT]?[sS]?)` -const jouleRegexStr = `^([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]?)` -const eventsRegexStr = `^([eE][vV]?[eE]?[nN][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 wattRegex = regexp.MustCompile(wattRegexStr) -var jouleRegex = regexp.MustCompile(jouleRegexStr) -var cyclesRegex = regexp.MustCompile(cyclesRegexStr) -var requestsRegex = regexp.MustCompile(requestsRegexStr) -var packetsRegex = regexp.MustCompile(packetsRegexStr) -var eventsRegex = regexp.MustCompile(eventsRegexStr) - // NewMeasure creates a new measure out of a string representing a measure like 'Bytes', 'Flops' and 'precent'. // It uses regular expressions for matching. 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 = wattRegex.FindStringSubmatch(unit) - if match != nil { - return Watt - } - match = jouleRegex.FindStringSubmatch(unit) - if match != nil { - return Joule - } - match = requestsRegex.FindStringSubmatch(unit) - if match != nil { - return Requests - } - match = packetsRegex.FindStringSubmatch(unit) - if match != nil { - return Packets - } - match = eventsRegex.FindStringSubmatch(unit) - if match != nil { - return Events + for m, data := range MeasuresMap { + regex := regexp.MustCompile(data.Regex) + match := regex.FindStringSubmatch(unit) + if match != nil { + return m + } } return InvalidMeasure } diff --git a/ccUnitPrefix.go b/ccUnitPrefix.go index 722b3bd..a6d3091 100644 --- a/ccUnitPrefix.go +++ b/ccUnitPrefix.go @@ -1,6 +1,8 @@ package ccunits -import "regexp" +import ( + "regexp" +) type Prefix float64 @@ -27,148 +29,146 @@ const ( Zebi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 Yobi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 ) -const prefixRegexStr = `^([kKmMgGtTpP]?[i]?)(.*)` +const PrefixUnitSplitRegexStr = `^([kKmMgGtTpPeEzZyY]?[i]?)(.*)` -var prefixRegex = regexp.MustCompile(prefixRegexStr) +var prefixUnitSplitRegex = regexp.MustCompile(PrefixUnitSplitRegexStr) + +type PrefixData struct { + Long string + Short string + Regex string +} + +// Different names and regex used for input and output +var InvalidPrefixLong string = "Invalid" +var InvalidPrefixShort string = "inval" +var PrefixDataMap map[Prefix]PrefixData = map[Prefix]PrefixData{ + Base: { + Long: "", + Short: "", + Regex: "^$", + }, + Kilo: { + Long: "Kilo", + Short: "K", + Regex: "^[kK]$", + }, + Mega: { + Long: "Mega", + Short: "M", + Regex: "^[M]$", + }, + Giga: { + Long: "Giga", + Short: "G", + Regex: "^[gG]$", + }, + Tera: { + Long: "Tera", + Short: "T", + Regex: "^[tT]$", + }, + Peta: { + Long: "Peta", + Short: "P", + Regex: "^[pP]$", + }, + Exa: { + Long: "Exa", + Short: "E", + Regex: "^[eE]$", + }, + Zetta: { + Long: "Zetta", + Short: "Z", + Regex: "^[zZ]$", + }, + Yotta: { + Long: "Yotta", + Short: "Y", + Regex: "^[yY]$", + }, + Milli: { + Long: "Milli", + Short: "m", + Regex: "^[m]$", + }, + Micro: { + Long: "Micro", + Short: "u", + Regex: "^[u]$", + }, + Nano: { + Long: "Nano", + Short: "n", + Regex: "^[n]$", + }, + Kibi: { + Long: "Kibi", + Short: "Ki", + Regex: "^[kK][i]$", + }, + Mebi: { + Long: "Mebi", + Short: "Mi", + Regex: "^[M][i]$", + }, + Gibi: { + Long: "Gibi", + Short: "Gi", + Regex: "^[gG][i]$", + }, + Tebi: { + Long: "Tebi", + Short: "Ti", + Regex: "^[tT][i]$", + }, + Pebi: { + Long: "Pebi", + Short: "Pi", + Regex: "^[pP][i]$", + }, + Exbi: { + Long: "Exbi", + Short: "Ei", + Regex: "^[eE][i]$", + }, + Zebi: { + Long: "Zebi", + Short: "Zi", + Regex: "^[zZ][i]$", + }, + Yobi: { + Long: "Yobi", + Short: "Yi", + Regex: "^[yY][i]$", + }, +} // String returns the long string for the prefix like 'Kilo' or 'Mega' -func (s *Prefix) String() string { - switch *s { - case InvalidPrefix: - return "Inval" - case Base: - return "" - case Kilo: - return "Kilo" - case Mega: - return "Mega" - case Giga: - return "Giga" - case Tera: - return "Tera" - case Peta: - return "Peta" - case Exa: - return "Exa" - case Zetta: - return "Zetta" - case Yotta: - return "Yotta" - 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 (p *Prefix) String() string { + if data, ok := PrefixDataMap[*p]; ok { + return data.Long } + return InvalidMeasureLong } // Prefix returns the short string for the prefix like 'K', 'M' or 'G'. Is is recommened to use Prefix() over String(). -func (s *Prefix) Prefix() string { - switch *s { - case InvalidPrefix: - return "" - case Base: - return "" - case Kilo: - return "K" - case Mega: - return "M" - case Giga: - return "G" - case Tera: - return "T" - case Peta: - return "P" - case Exa: - return "E" - case Zetta: - return "Z" - case Yotta: - return "Y" - 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 "" +func (p *Prefix) Prefix() string { + if data, ok := PrefixDataMap[*p]; ok { + return data.Short } + return InvalidMeasureShort } // NewPrefix creates a new prefix out of a string representing a unit like 'k', 'K', 'M' or 'G'. func NewPrefix(prefix string) Prefix { - 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 "p": - return Peta - case "P": - return Peta - case "e": - 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": - 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 InvalidPrefix + for p, data := range PrefixDataMap { + regex := regexp.MustCompile(data.Regex) + match := regex.FindStringSubmatch(prefix) + if match != nil { + return p + } } + return InvalidPrefix } diff --git a/ccUnits.go b/ccUnits.go index 78b2c89..347a242 100644 --- a/ccUnits.go +++ b/ccUnits.go @@ -1,3 +1,4 @@ +// Unit system for cluster monitoring metrics like bytes, flops and events package ccunits import ( @@ -204,7 +205,7 @@ func NewUnit(unitStr string) Unit { measure: InvalidMeasure, divMeasure: InvalidMeasure, } - matches := prefixRegex.FindStringSubmatch(unitStr) + matches := prefixUnitSplitRegex.FindStringSubmatch(unitStr) if len(matches) > 2 { pre := NewPrefix(matches[1]) measures := strings.Split(matches[2], "/")