143 lines
4.7 KiB
Go
143 lines
4.7 KiB
Go
// Copyright 2021 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 go1.17
|
|
// +build go1.17
|
|
|
|
package internal
|
|
|
|
import (
|
|
"math"
|
|
"path"
|
|
"runtime/metrics"
|
|
"strings"
|
|
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
// RuntimeMetricsToProm produces a Prometheus metric name from a runtime/metrics
|
|
// metric description and validates whether the metric is suitable for integration
|
|
// with Prometheus.
|
|
//
|
|
// Returns false if a name could not be produced, or if Prometheus does not understand
|
|
// the runtime/metrics Kind.
|
|
//
|
|
// Note that the main reason a name couldn't be produced is if the runtime/metrics
|
|
// package exports a name with characters outside the valid Prometheus metric name
|
|
// character set. This is theoretically possible, but should never happen in practice.
|
|
// Still, don't rely on it.
|
|
func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool) {
|
|
namespace := "go"
|
|
|
|
comp := strings.SplitN(d.Name, ":", 2)
|
|
key := comp[0]
|
|
unit := comp[1]
|
|
|
|
// The last path element in the key is the name,
|
|
// the rest is the subsystem.
|
|
subsystem := path.Dir(key[1:] /* remove leading / */)
|
|
name := path.Base(key)
|
|
|
|
// subsystem is translated by replacing all / and - with _.
|
|
subsystem = strings.ReplaceAll(subsystem, "/", "_")
|
|
subsystem = strings.ReplaceAll(subsystem, "-", "_")
|
|
|
|
// unit is translated assuming that the unit contains no
|
|
// non-ASCII characters.
|
|
unit = strings.ReplaceAll(unit, "-", "_")
|
|
unit = strings.ReplaceAll(unit, "*", "_")
|
|
unit = strings.ReplaceAll(unit, "/", "_per_")
|
|
|
|
// name has - replaced with _ and is concatenated with the unit and
|
|
// other data.
|
|
name = strings.ReplaceAll(name, "-", "_")
|
|
name += "_" + unit
|
|
if d.Cumulative && d.Kind != metrics.KindFloat64Histogram {
|
|
name += "_total"
|
|
}
|
|
|
|
valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name))
|
|
switch d.Kind {
|
|
case metrics.KindUint64:
|
|
case metrics.KindFloat64:
|
|
case metrics.KindFloat64Histogram:
|
|
default:
|
|
valid = false
|
|
}
|
|
return namespace, subsystem, name, valid
|
|
}
|
|
|
|
// RuntimeMetricsBucketsForUnit takes a set of buckets obtained for a runtime/metrics histogram
|
|
// type (so, lower-bound inclusive) and a unit from a runtime/metrics name, and produces
|
|
// a reduced set of buckets. This function always removes any -Inf bucket as it's represented
|
|
// as the bottom-most upper-bound inclusive bucket in Prometheus.
|
|
func RuntimeMetricsBucketsForUnit(buckets []float64, unit string) []float64 {
|
|
switch unit {
|
|
case "bytes":
|
|
// Re-bucket as powers of 2.
|
|
return reBucketExp(buckets, 2)
|
|
case "seconds":
|
|
// Re-bucket as powers of 10 and then merge all buckets greater
|
|
// than 1 second into the +Inf bucket.
|
|
b := reBucketExp(buckets, 10)
|
|
for i := range b {
|
|
if b[i] <= 1 {
|
|
continue
|
|
}
|
|
b[i] = math.Inf(1)
|
|
b = b[:i+1]
|
|
break
|
|
}
|
|
return b
|
|
}
|
|
return buckets
|
|
}
|
|
|
|
// reBucketExp takes a list of bucket boundaries (lower bound inclusive) and
|
|
// downsamples the buckets to those a multiple of base apart. The end result
|
|
// is a roughly exponential (in many cases, perfectly exponential) bucketing
|
|
// scheme.
|
|
func reBucketExp(buckets []float64, base float64) []float64 {
|
|
bucket := buckets[0]
|
|
var newBuckets []float64
|
|
// We may see a -Inf here, in which case, add it and skip it
|
|
// since we risk producing NaNs otherwise.
|
|
//
|
|
// We need to preserve -Inf values to maintain runtime/metrics
|
|
// conventions. We'll strip it out later.
|
|
if bucket == math.Inf(-1) {
|
|
newBuckets = append(newBuckets, bucket)
|
|
buckets = buckets[1:]
|
|
bucket = buckets[0]
|
|
}
|
|
// From now on, bucket should always have a non-Inf value because
|
|
// Infs are only ever at the ends of the bucket lists, so
|
|
// arithmetic operations on it are non-NaN.
|
|
for i := 1; i < len(buckets); i++ {
|
|
if bucket >= 0 && buckets[i] < bucket*base {
|
|
// The next bucket we want to include is at least bucket*base.
|
|
continue
|
|
} else if bucket < 0 && buckets[i] < bucket/base {
|
|
// In this case the bucket we're targeting is negative, and since
|
|
// we're ascending through buckets here, we need to divide to get
|
|
// closer to zero exponentially.
|
|
continue
|
|
}
|
|
// The +Inf bucket will always be the last one, and we'll always
|
|
// end up including it here because bucket
|
|
newBuckets = append(newBuckets, bucket)
|
|
bucket = buckets[i]
|
|
}
|
|
return append(newBuckets, bucket)
|
|
}
|