// Copyright 2014 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 metrics /* #cgo pkg-config: milvus_segcore milvus_common #include #include "segcore/metrics_c.h" */ import "C" import ( "sort" "strings" "sync" "unsafe" "github.com/milvus-io/milvus/pkg/log" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/expfmt" "go.uber.org/zap" 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 } func NewCRegistry() *CRegistry { return &CRegistry{ Registry: prometheus.NewRegistry(), } } // only re-write the implementation of Gather() type CRegistry struct { *prometheus.Registry mtx sync.RWMutex } // Gather implements Gatherer. func (r *CRegistry) Gather() (res []*dto.MetricFamily, err error) { var ( parser expfmt.TextParser ) r.mtx.RLock() cMetricsStr := C.GetKnowhereMetrics() metricsStr := C.GoString(cMetricsStr) C.free(unsafe.Pointer(cMetricsStr)) out, err := parser.TextToMetricFamilies(strings.NewReader(metricsStr)) if err != nil { log.Error("fail to parse prometheus metrics", zap.Error(err)) return } res = NormalizeMetricFamilies(out) return }