| // Copyright 2018 The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package internal |
| |
| import ( |
| "sort" |
| |
| dto "github.com/prometheus/client_model/go" |
| ) |
| |
| // metricSorter is a sortable slice of *dto.Metric. |
| type metricSorter []*dto.Metric |
| |
| func (s metricSorter) Len() int { |
| return len(s) |
| } |
| |
| func (s metricSorter) Swap(i, j int) { |
| s[i], s[j] = s[j], s[i] |
| } |
| |
| func (s metricSorter) Less(i, j int) bool { |
| if len(s[i].Label) != len(s[j].Label) { |
| // This should not happen. The metrics are |
| // inconsistent. However, we have to deal with the fact, as |
| // people might use custom collectors or metric family injection |
| // to create inconsistent metrics. So let's simply compare the |
| // number of labels in this case. That will still yield |
| // reproducible sorting. |
| return len(s[i].Label) < len(s[j].Label) |
| } |
| for n, lp := range s[i].Label { |
| vi := lp.GetValue() |
| vj := s[j].Label[n].GetValue() |
| if vi != vj { |
| return vi < vj |
| } |
| } |
| |
| // We should never arrive here. Multiple metrics with the same |
| // label set in the same scrape will lead to undefined ingestion |
| // behavior. However, as above, we have to provide stable sorting |
| // here, even for inconsistent metrics. So sort equal metrics |
| // by their timestamp, with missing timestamps (implying "now") |
| // coming last. |
| if s[i].TimestampMs == nil { |
| return false |
| } |
| if s[j].TimestampMs == nil { |
| return true |
| } |
| return s[i].GetTimestampMs() < s[j].GetTimestampMs() |
| } |
| |
| // NormalizeMetricFamilies returns a MetricFamily slice with empty |
| // MetricFamilies pruned and the remaining MetricFamilies sorted by name within |
| // the slice, with the contained Metrics sorted within each MetricFamily. |
| func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily { |
| for _, mf := range metricFamiliesByName { |
| sort.Sort(metricSorter(mf.Metric)) |
| } |
| names := make([]string, 0, len(metricFamiliesByName)) |
| for name, mf := range metricFamiliesByName { |
| if len(mf.Metric) > 0 { |
| names = append(names, name) |
| } |
| } |
| sort.Strings(names) |
| result := make([]*dto.MetricFamily, 0, len(names)) |
| for _, name := range names { |
| result = append(result, metricFamiliesByName[name]) |
| } |
| return result |
| } |