mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25: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