mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-31 16:05:06 +01:00 
			
		
		
		
	Add convenience routines to unit package
This commit is contained in:
		| @@ -366,3 +366,16 @@ func loadJobStat(job *schema.JobMeta, metric string) float64 { | |||||||
|  |  | ||||||
| 	return 0.0 | 	return 0.0 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func checkJobData(d *schema.JobData) error { | ||||||
|  | 	// for name, scopes := range *d { | ||||||
|  |  | ||||||
|  | 	// 	for scope, metric := range scopes { | ||||||
|  | 	// 		// 1. Unit normalisation | ||||||
|  |  | ||||||
|  | 	// 	} | ||||||
|  | 	// 	// 2. Add node scope if missing | ||||||
|  |  | ||||||
|  | 	// } | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| # cc-units - 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](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). | 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: | In order to enable unit comparison and conversion, the ccUnits package provides some helpers: | ||||||
| ```go | ```go | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package units | package units | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"math" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -172,3 +173,20 @@ func NewPrefix(prefix string) Prefix { | |||||||
| 	} | 	} | ||||||
| 	return InvalidPrefix | 	return InvalidPrefix | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func getExponent(p float64) int { | ||||||
|  | 	count := 0 | ||||||
|  |  | ||||||
|  | 	for p > 1.0 { | ||||||
|  | 		p = p / 1000.0 | ||||||
|  | 		count++ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return count * 3 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewPrefixFromFactor(op Prefix, e int) Prefix { | ||||||
|  | 	f := float64(op) | ||||||
|  | 	exp := math.Pow10(getExponent(f) - e) | ||||||
|  | 	return Prefix(exp) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package units | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"math" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -25,7 +26,9 @@ type Unit interface { | |||||||
|  |  | ||||||
| var INVALID_UNIT = NewUnit("foobar") | 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. | // 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 { | func (u *unit) Valid() bool { | ||||||
| 	return u.prefix != InvalidPrefix && u.measure != InvalidMeasure | 	return u.prefix != InvalidPrefix && u.measure != InvalidMeasure | ||||||
| } | } | ||||||
| @@ -71,6 +74,63 @@ func (u *unit) getUnitDenominator() Measure { | |||||||
| 	return u.divMeasure | 	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, nu *string) { | ||||||
|  | 	u := NewUnit(us) | ||||||
|  | 	f, e := getNormalizationFactor((*v)) | ||||||
|  | 	*v = math.Ceil(*v * f) | ||||||
|  | 	u.setPrefix(NewPrefixFromFactor(u.getPrefix(), e)) | ||||||
|  | 	*nu = u.Short() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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() | ||||||
|  | } | ||||||
|  |  | ||||||
| // GetPrefixPrefixFactor creates the default conversion function between two prefixes. | // GetPrefixPrefixFactor creates the default conversion function between two prefixes. | ||||||
| // It returns a conversation function for the value. | // It returns a conversation function for the value. | ||||||
| func GetPrefixPrefixFactor(in Prefix, out Prefix) func(value interface{}) interface{} { | func GetPrefixPrefixFactor(in Prefix, out Prefix) func(value interface{}) interface{} { | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package units | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| @@ -199,3 +200,108 @@ func TestPrefixRegex(t *testing.T) { | |||||||
| 		t.Logf("succussfully compiled regex '%s' for prefix %s", data.Regex, data.Long) | 		t.Logf("succussfully compiled regex '%s' for prefix %s", data.Regex, data.Long) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestConvertValue(t *testing.T) { | ||||||
|  | 	v := float64(103456) | ||||||
|  | 	ConvertValue(&v, "MB/s", "GB/s") | ||||||
|  |  | ||||||
|  | 	if v != 104.00 { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 103.456, Got %f", v) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestConvertValueUp(t *testing.T) { | ||||||
|  | 	v := float64(10.3456) | ||||||
|  | 	ConvertValue(&v, "GB/s", "MB/s") | ||||||
|  |  | ||||||
|  | 	if v != 10346.00 { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 10346.00, Got %f", v) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | func TestConvertSeries(t *testing.T) { | ||||||
|  | 	s := []float64{2890031237, 23998994567, 389734042344, 390349424345} | ||||||
|  | 	r := []float64{3, 24, 390, 391} | ||||||
|  | 	ConvertSeries(s, "F/s", "GF/s") | ||||||
|  |  | ||||||
|  | 	if !reflect.DeepEqual(s, r) { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 3, 24, 390, 391, Got %v", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNormalizeValue(t *testing.T) { | ||||||
|  | 	var s string | ||||||
|  | 	v := float64(103456) | ||||||
|  |  | ||||||
|  | 	NormalizeValue(&v, "MB/s", &s) | ||||||
|  |  | ||||||
|  | 	if v != 104.00 { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 104.00, Got %f", v) | ||||||
|  | 	} | ||||||
|  | 	if s != "GB/s" { | ||||||
|  | 		t.Errorf("Failed Prefix or unit: Want GB/s, Got %s", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNormalizeValueNoPrefix(t *testing.T) { | ||||||
|  | 	var s string | ||||||
|  | 	v := float64(103458596) | ||||||
|  |  | ||||||
|  | 	NormalizeValue(&v, "F/s", &s) | ||||||
|  |  | ||||||
|  | 	if v != 104.00 { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 104.00, Got %f", v) | ||||||
|  | 	} | ||||||
|  | 	if s != "MFlops/s" { | ||||||
|  | 		t.Errorf("Failed Prefix or unit: Want GB/s, Got %s", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNormalizeValueKeep(t *testing.T) { | ||||||
|  | 	var s string | ||||||
|  | 	v := float64(345) | ||||||
|  |  | ||||||
|  | 	NormalizeValue(&v, "MB/s", &s) | ||||||
|  |  | ||||||
|  | 	if v != 345.00 { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 104.00, Got %f", v) | ||||||
|  | 	} | ||||||
|  | 	if s != "MB/s" { | ||||||
|  | 		t.Errorf("Failed Prefix or unit: Want GB/s, Got %s", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNormalizeValueDown(t *testing.T) { | ||||||
|  | 	var s string | ||||||
|  | 	v := float64(0.0004578) | ||||||
|  |  | ||||||
|  | 	NormalizeValue(&v, "GB/s", &s) | ||||||
|  |  | ||||||
|  | 	if v != 458.00 { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 458.00, Got %f", v) | ||||||
|  | 	} | ||||||
|  | 	if s != "KB/s" { | ||||||
|  | 		t.Errorf("Failed Prefix or unit: Want KB/s, Got %s", s) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNormalizeSeries(t *testing.T) { | ||||||
|  | 	var us string | ||||||
|  | 	s := []float64{2890031237, 23998994567, 389734042344, 390349424345} | ||||||
|  | 	r := []float64{3, 24, 390, 391} | ||||||
|  |  | ||||||
|  | 	total := 0.0 | ||||||
|  | 	for _, number := range s { | ||||||
|  | 		total += number | ||||||
|  | 	} | ||||||
|  | 	avg := total / float64(len(s)) | ||||||
|  |  | ||||||
|  | 	fmt.Printf("AVG: %e\n", avg) | ||||||
|  | 	NormalizeSeries(s, avg, "KB/s", &us) | ||||||
|  |  | ||||||
|  | 	if !reflect.DeepEqual(s, r) { | ||||||
|  | 		t.Errorf("Failed ConvertValue: Want 3, 24, 390, 391, Got %v", s) | ||||||
|  | 	} | ||||||
|  | 	if us != "TB/s" { | ||||||
|  | 		t.Errorf("Failed Prefix or unit: Want TB/s, Got %s", us) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user