diff --git a/internal/metricRouter/README.md b/internal/metricRouter/README.md index fe2d64f..8b6dd5b 100644 --- a/internal/metricRouter/README.md +++ b/internal/metricRouter/README.md @@ -52,6 +52,11 @@ The CCMetric router sits in between the collectors and the sinks and can be used ], "rename_metrics" : { "metric_12345" : "mymetric" + }, + "normalize_units" : true, + "change_unit_prefix" { + "mem_used" : "G", + "mem_total" : "G" } } ``` @@ -192,6 +197,14 @@ This option takes a list of evaluable conditions and performs them one after the ``` The first line is comparable with the example in `drop_metrics`, it drops all metrics starting with `drop_metric_` and ending with a number. The second line drops all metrics of the first hardware thread (**not** recommended) +# Manipulating the metric units + +## The `normalize_units` option +The cc-metric-collector tries to read the data from the system as it is reported. If available, it tries to read the metric unit from the system as well (e.g. from `/proc/meminfo`). The problem is that, depending on the source, the metric units are named differently. Just think about `byte`, `Byte`, `B`, `bytes`, ... +The [cc-units](https://github.com/ClusterCockpit/cc-units) package provides us a normalization option to use the same metric unit name for all metrics. It this option is set to true, all `unit` meta tags are normalized. + +## The `change_unit_prefix` section +It is often the case that metrics are reported by the system using a rather outdated unit prefix (like `/proc/meminfo` still uses kByte despite current memory sizes are in the GByte range). If you want to change the prefix of a unit, you can do that with the help of [cc-units](https://github.com/ClusterCockpit/cc-units). The setting works on the metric name and requires the new prefix for the metric. The cc-units package determines the scaling factor. # Aggregate metric values of the current interval with the `interval_aggregates` option @@ -239,3 +252,22 @@ Use cases for `interval_aggregates`: } } ``` + +# Order of operations + +The router performs the above mentioned options in a specific order. In order to get the logic you want for a specific metric, it is crucial to know the processing order: + +- Add the `hostname` tag (c) +- Manipulate the timestamp to the interval timestamp (c,r) +- Drop metrics based on `drop_metrics` and `drop_metrics_if` (c,r) +- Add tags based on `add_tags` (c,r) +- Delete tags based on `del_tags` (c,r) +- Rename metric based on `rename_metric` (c,r) + - Add tags based on `add_tags` to still work if the configuration uses the new name (c,r) + - Delete tags based on `del_tags` to still work if the configuration uses the new name (c,r) +- Normalize units when `normalize_units` is set (c,r) +- Convert unit prefix based on `change_unit_prefix` (c,r) + +Legend: +- 'c' if metric is coming from a collector +- 'r' if metric is coming from a receiver \ No newline at end of file diff --git a/internal/metricRouter/metricRouter.go b/internal/metricRouter/metricRouter.go index 8875d0e..2614ced 100644 --- a/internal/metricRouter/metricRouter.go +++ b/internal/metricRouter/metricRouter.go @@ -12,6 +12,7 @@ import ( lp "github.com/ClusterCockpit/cc-metric-collector/internal/ccMetric" agg "github.com/ClusterCockpit/cc-metric-collector/internal/metricAggregator" mct "github.com/ClusterCockpit/cc-metric-collector/internal/multiChanTicker" + units "github.com/ClusterCockpit/cc-units" ) const ROUTER_MAX_FORWARD = 50 @@ -35,6 +36,8 @@ type metricRouterConfig struct { IntervalStamp bool `json:"interval_timestamp"` // Update timestamp periodically by ticker each interval? NumCacheIntervals int `json:"num_cache_intervals"` // Number of intervals of cached metrics for evaluation MaxForward int `json:"max_forward"` // Number of maximal forwarded metrics at one select + NormalizeUnits bool `json:"normalize_units"` // Check unit meta flag and normalize it using cc-units + ChangeUnitPrefix map[string]string `json:"change_unit_prefix"` // Add prefix that should be applied to the metrics dropMetrics map[string]bool // Internal map for O(1) lookup } @@ -207,6 +210,38 @@ func (r *metricRouter) dropMetric(point lp.CCMetric) bool { return false } +func (r *metricRouter) prepareUnit(point lp.CCMetric) bool { + if r.config.NormalizeUnits { + if in_unit, ok := point.GetMeta("unit"); ok { + u := units.NewUnit(in_unit) + if u.Valid() { + point.AddMeta("unit", u.Short()) + } + } + } + if newP, ok := r.config.ChangeUnitPrefix[point.Name()]; ok { + + newPrefix := units.NewPrefix(newP) + + if in_unit, ok := point.GetMeta("unit"); ok && newPrefix != units.InvalidPrefix { + u := units.NewUnit(in_unit) + if u.Valid() { + cclog.ComponentDebug("MetricRouter", "Change prefix to", newP, "for metric", point.Name()) + conv, out_unit := units.GetUnitPrefixFactor(u, newPrefix) + if conv != nil && out_unit.Valid() { + if val, ok := point.GetField("value"); ok { + point.AddField("value", conv(val)) + point.AddMeta("unit", out_unit.Short()) + } + } + } + + } + } + + return true +} + // Start starts the metric router func (r *metricRouter) Start() { // start timer if configured @@ -232,9 +267,11 @@ func (r *metricRouter) Start() { if new, ok := r.config.RenameMetrics[name]; ok { point.SetName(new) point.AddMeta("oldname", name) + r.DoAddTags(point) + r.DoDelTags(point) } - r.DoAddTags(point) - r.DoDelTags(point) + + r.prepareUnit(point) for _, o := range r.outputs { o <- point