diff --git a/collector/manager/metrics_collector_manager.go b/collector/manager/metrics_collector_manager.go index 73cc334..b3c30c4 100644 --- a/collector/manager/metrics_collector_manager.go +++ b/collector/manager/metrics_collector_manager.go @@ -182,8 +182,18 @@ func (mcm *MetricsCollectorManager) declareMetricDefinitions(liveMetrics []hmetr } } + // the metric tags will consist of the custom tags as well as the fixed tags metricTags := metric.Tags.ExpandTokens(false, additionalEnv) + units, err := collector.GetMetricUnits(metric.Units) + if err == nil { + if units.Symbol != "" { + metricTags["units"] = units.Symbol + } + } else { + glog.Warningf("Units for metric definition [%v] for endpoint [%v] is invalid. No units will be assigned. err=%v", metric.ID, endpoint.String(), err) + } + // put all the tags together for the full list of tags to be applied to this metric definition allMetricTags := tags.Tags{} allMetricTags.AppendTags(globalTags) // global tags are overridden by... allMetricTags.AppendTags(endpointTags) // endpoint tags which are overridden by... diff --git a/collector/metric_units.go b/collector/metric_units.go new file mode 100644 index 0000000..5200e84 --- /dev/null +++ b/collector/metric_units.go @@ -0,0 +1,109 @@ +package collector + +import ( + "fmt" + "strings" +) + +type MetricUnits struct { + Symbol string + Custom bool +} + +const customMetricUnitsPrefix = "custom:" + +type standardMetricUnits []MetricUnits + +// standardMetricUnitsList is a list of standard metric units +// See https://en.wikipedia.org/wiki/International_System_of_Units and http://metrics20.org/spec/ +var standardMetricUnitsList = standardMetricUnits{ + // absolute sizes in bytes + {Symbol: "B"}, + {Symbol: "kB"}, + {Symbol: "MB"}, + {Symbol: "GB"}, + {Symbol: "TB"}, + {Symbol: "PB"}, + {Symbol: "KiB"}, + {Symbol: "MiB"}, + {Symbol: "GiB"}, + {Symbol: "TiB"}, + {Symbol: "PiB"}, + + // absolute sizes in bits + {Symbol: "b"}, + {Symbol: "kb"}, + {Symbol: "Mb"}, + {Symbol: "Gb"}, + {Symbol: "Tb"}, + {Symbol: "Pb"}, + {Symbol: "Kib"}, + {Symbol: "Mib"}, + {Symbol: "Gib"}, + {Symbol: "Tib"}, + {Symbol: "Pib"}, + + // relative time + {Symbol: "jiff"}, + {Symbol: "ns"}, + {Symbol: "us"}, + {Symbol: "ms"}, + {Symbol: "s"}, + {Symbol: "M"}, + {Symbol: "h"}, + {Symbol: "d"}, + {Symbol: "w"}, + + // frequency + {Symbol: "Hz"}, + {Symbol: "kHz"}, + {Symbol: "MHz"}, + {Symbol: "GHz"}, + + // temperature + {Symbol: "C"}, + {Symbol: "F"}, + {Symbol: "K"}, + + // current + {Symbol: "uA"}, + {Symbol: "mA"}, + {Symbol: "A"}, + {Symbol: "kA"}, + {Symbol: "MA"}, + {Symbol: "GA"}, + + // voltage + {Symbol: "uV"}, + {Symbol: "mV"}, + {Symbol: "V"}, + {Symbol: "kV"}, + {Symbol: "MV"}, + {Symbol: "GV"}, + + // percentage + {Symbol: "%"}, + + // none (no metric units are applicable) + {Symbol: ""}, +} + +// GetMetricUnits will check to see if the given string is a valid units identifier. +// If it is valid, this returns the units string as a MetricUnits. +// If it is not a valid units identifier, an error is returned. +// If it is a custom units identifier, it is returned minus the custom units prefix. +func GetMetricUnits(u string) (MetricUnits, error) { + if len(u) > len(customMetricUnitsPrefix) && strings.HasPrefix(u, customMetricUnitsPrefix) { + mu := MetricUnits{ + Symbol: strings.TrimPrefix(u, customMetricUnitsPrefix), + Custom: true, + } + return mu, nil + } + for _, x := range standardMetricUnitsList { + if x.Symbol == u { + return x, nil + } + } + return MetricUnits{}, fmt.Errorf("invalid metric units: %v", u) +} diff --git a/collector/metric_units_test.go b/collector/metric_units_test.go new file mode 100644 index 0000000..dda4b41 --- /dev/null +++ b/collector/metric_units_test.go @@ -0,0 +1,51 @@ +package collector + +import ( + "testing" +) + +func TestGetMetricUnits(t *testing.T) { + // make sure valid metrics are retrieved successfully + u, e := GetMetricUnits("ms") + if e != nil { + t.Errorf("Should not have failed. Error=%v", e) + } + if u.Symbol != "ms" { + t.Errorf("Should have matched 'ms'. u=%v", u) + } + if u.Custom != false { + t.Errorf("Should not have been custom. u=%v", u) + } + + u, e = GetMetricUnits("custom:foobars") + if e != nil { + t.Errorf("Should not have failed. Error=%v", e) + } + if u.Symbol != "foobars" { + t.Errorf("Should have matched 'foobars'. u=%v", u) + } + if u.Custom != true { + t.Errorf("Should have been custom. u=%v", u) + } + + u, e = GetMetricUnits("") + if e != nil { + t.Errorf("Should not have failed. Empty units means 'none'. Error=%v", e) + } + if u.Symbol != "" { + t.Errorf("Should have matched the 'none' units (an empty string). u=%v", u) + } + if u.Custom != false { + t.Errorf("Should not have been custom. u=%v", u) + } + + // make sure errors are generated properly + u, e = GetMetricUnits("millis") + if e == nil { + t.Errorf("Should have failed - not a standard metric. u=%v", u) + } + u, e = GetMetricUnits("foobars") + if e == nil { + t.Errorf("Should have failed - not a standard metric. u=%v", u) + } +} diff --git a/collector/metrics_endpoint.go b/collector/metrics_endpoint.go index 552672f..78693a8 100644 --- a/collector/metrics_endpoint.go +++ b/collector/metrics_endpoint.go @@ -41,10 +41,11 @@ const ( // Tags specified here will be attached to the metric when stored to Hawkular Metrics. // USED FOR YAML type MonitoredMetric struct { - ID string ",omitempty" - Name string - Type metrics.MetricType - Tags tags.Tags ",omitempty" + ID string ",omitempty" + Name string + Type metrics.MetricType + Units string ",omitempty" + Tags tags.Tags ",omitempty" } // Endpoint provides information about how to connect to a particular endpoint in order @@ -64,7 +65,7 @@ type Endpoint struct { } func (m *MonitoredMetric) String() string { - return fmt.Sprintf("Metric: id=[%v], name=[%v], type=[%v], tags=[%v]", m.ID, m.Name, m.Type, m.Tags) + return fmt.Sprintf("Metric: id=[%v], name=[%v], type=[%v], units=[%v], tags=[%v]", m.ID, m.Name, m.Type, m.Units, m.Tags) } func (e *Endpoint) String() string { @@ -115,6 +116,10 @@ func (e *Endpoint) ValidateEndpoint() error { } } + if _, err := GetMetricUnits(m.Units); err != nil { + return fmt.Errorf("Endpoint [%v] metric [%v] has invalid units [%v]", e.URL, m.Name, m.Units) + } + // if there is no metric ID given, just use the metric name itself if m.ID == "" { e.Metrics[i].ID = m.Name