mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-10-17 10:09:05 +02:00
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
168 lines
4.7 KiB
Go
168 lines
4.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Copyright 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.
|
|
|
|
//go:build windows
|
|
|
|
package httphandler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
dto "github.com/prometheus/client_model/go"
|
|
)
|
|
|
|
// Interface guard.
|
|
var _ prometheus.Gatherer = mergedGatherer{}
|
|
|
|
// mergedGatherer combines the results of two registries.
|
|
//
|
|
// prometheus.Gatherers re-hashes and re-checks every metric that the registries
|
|
// already checked in their own Gather. mergedGatherer merges the sorted results
|
|
// without the second check, as long as no metric family name of one registry
|
|
// collides with the other. If a name collides, it hands both results to
|
|
// prometheus.Gatherers, so the output and errors stay the same as before.
|
|
// See https://github.com/prometheus/client_golang/pull/1702.
|
|
type mergedGatherer struct {
|
|
first prometheus.Gatherer
|
|
second prometheus.Gatherer
|
|
}
|
|
|
|
func (g mergedGatherer) Gather() ([]*dto.MetricFamily, error) {
|
|
firstMFs, firstErr := g.first.Gather()
|
|
secondMFs, secondErr := g.second.Gather()
|
|
|
|
if namesCollide(firstMFs, secondMFs) {
|
|
return prometheus.Gatherers{
|
|
gathered(firstMFs, firstErr),
|
|
gathered(secondMFs, secondErr),
|
|
}.Gather()
|
|
}
|
|
|
|
var errs prometheus.MultiError
|
|
|
|
errs = appendGathererErrors(errs, 1, firstErr)
|
|
errs = appendGathererErrors(errs, 2, secondErr)
|
|
|
|
return mergeSorted(firstMFs, secondMFs), errs.MaybeUnwrap()
|
|
}
|
|
|
|
// gathered returns a Gatherer that returns an earlier Gather result.
|
|
func gathered(mfs []*dto.MetricFamily, err error) prometheus.GathererFunc {
|
|
return prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) {
|
|
return mfs, err
|
|
})
|
|
}
|
|
|
|
// appendGathererErrors adds err to errs like prometheus.Gatherers does.
|
|
func appendGathererErrors(errs prometheus.MultiError, gatherer int, err error) prometheus.MultiError {
|
|
if err == nil {
|
|
return errs
|
|
}
|
|
|
|
var multiErr prometheus.MultiError
|
|
if !errors.As(err, &multiErr) {
|
|
return append(errs, fmt.Errorf("[from Gatherer #%d] %w", gatherer, err))
|
|
}
|
|
|
|
for _, err := range multiErr {
|
|
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", gatherer, err))
|
|
}
|
|
|
|
return errs
|
|
}
|
|
|
|
// namesCollide reports whether a metric family name of a can clash with one of b.
|
|
// Besides equal names, it counts a name that equals the other name plus a
|
|
// summary or histogram suffix, regardless of the types. A false positive only
|
|
// means the scrape takes the slower prometheus.Gatherers path.
|
|
func namesCollide(a, b []*dto.MetricFamily) bool {
|
|
if len(a) == 0 || len(b) == 0 {
|
|
return false
|
|
}
|
|
|
|
// Index the smaller result. The exporter metrics registry has only a few families.
|
|
if len(a) > len(b) {
|
|
a, b = b, a
|
|
}
|
|
|
|
// names holds the names of a, bases the names of a without a summary or histogram suffix.
|
|
names := make(map[string]struct{}, len(a))
|
|
bases := make(map[string]struct{})
|
|
|
|
for _, mf := range a {
|
|
names[mf.GetName()] = struct{}{}
|
|
|
|
if base, ok := trimHistogramSuffix(mf.GetName()); ok {
|
|
bases[base] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for _, mf := range b {
|
|
name := mf.GetName()
|
|
|
|
if _, ok := names[name]; ok {
|
|
return true
|
|
}
|
|
|
|
// A name of a is the name plus a suffix.
|
|
if _, ok := bases[name]; ok {
|
|
return true
|
|
}
|
|
|
|
// The name is a name of a plus a suffix.
|
|
if base, ok := trimHistogramSuffix(name); ok {
|
|
if _, ok := names[base]; ok {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// trimHistogramSuffix removes the suffixes the text formats add to summaries and histograms.
|
|
// They are the suffixes client_golang checks for collisions.
|
|
func trimHistogramSuffix(name string) (string, bool) {
|
|
for _, suffix := range [...]string{"_count", "_sum", "_bucket"} {
|
|
if base, ok := strings.CutSuffix(name, suffix); ok {
|
|
return base, true
|
|
}
|
|
}
|
|
|
|
return name, false
|
|
}
|
|
|
|
// mergeSorted merges two metric family slices that are sorted by name and share no name.
|
|
func mergeSorted(a, b []*dto.MetricFamily) []*dto.MetricFamily {
|
|
result := make([]*dto.MetricFamily, 0, len(a)+len(b))
|
|
|
|
for len(a) > 0 && len(b) > 0 {
|
|
if a[0].GetName() < b[0].GetName() {
|
|
result = append(result, a[0])
|
|
a = a[1:]
|
|
} else {
|
|
result = append(result, b[0])
|
|
b = b[1:]
|
|
}
|
|
}
|
|
|
|
result = append(result, a...)
|
|
|
|
return append(result, b...)
|
|
}
|