client_golang/prometheus/desc.go

370 lines
12 KiB
Go

package prometheus
import (
"errors"
"fmt"
"regexp"
"sort"
"strings"
"unicode/utf8"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
var (
metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
labelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
)
// reservedLabelPrefix is a prefix which is not legal in user-supplied
// label names.
const reservedLabelPrefix = "__"
// Labels represents a collection of label name -> value mappings. This type is
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
// metric vector Collectors, e.g.:
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
//
// The other use-case is the specification of constant label pairs in Opts or to
// create a Desc.
type Labels map[string]string
// Desc is used to describe the meta-data of a Metric (via its Desc method) or a
// group of metrics collected by a Collector (via its Describe method). Some of its
// methods are only used internally and are therefore not exported, which also
// prevents users to implement their own descriptors. Descriptor instances must
// be created via suitable NewXXXDesc functions and will in generally only be
// needed in custom collectors.
//
// Desc implementations are immutable by contract.
//
// A Desc that also implements the error interface is called an invalid
// descriptor, which is solely used to communicate an error and must never be
// processed further.
type Desc interface {
// String returns a string representation of the descriptor as usual. It
// is also used as an ID that must be unique among all descriptors
// registered by a Registry.
String() string
dims() string
}
// NewInvalidDesc returns a descriptor that also implements the error
// interface. It is used to communicate an error during a call of Desc (Metric
// method) or Describe (Collector method). Create with NewInvalidDesc.
func NewInvalidDesc(err error) Desc {
return &invalidDesc{err: err}
}
type invalidDesc struct {
err error
}
func (d *invalidDesc) Error() string {
return d.err.Error()
}
func (d *invalidDesc) String() string {
return "[invalid] " + d.err.Error()
}
func (d *invalidDesc) dims() string {
return ""
}
// NewPrefixDesc returns a descriptor that is used by Collectors that want to
// reserve a whole metric name prefix for their own use. An invalid descriptor
// is returned if the prefix is not a valid as the start of a metric
// name. However, an empty prefix is valid and reserves all metric names.
func NewPrefixDesc(prefix string) Desc {
if prefix != "" && !validMetricName(prefix) {
return NewInvalidDesc(fmt.Errorf("%q is not a valid metric name prefix", prefix))
}
return &prefixDesc{pfx: prefix}
}
type prefixDesc struct {
prefix string
}
func (d *prefixDesc) String() string {
return "[prefix] " + d.prefix
}
func (d *prefixDesc) dims() string {
return "" // PrefixDesc is for all dimensions.
}
func (d *prefixDesc) overlapsWith(other Desc) bool {
switch o := other.(type) {
case *invalidDesc:
// Invalid descs never overlap.
return false
case *partialDesc, *fullDesc:
return strings.HasPrefix(o.fqName, d.prefix)
case *prefixDesc:
return strings.HasPrefix(o.prefix, d.prefix) || strings.HasPrefix(d.Prefix, o.prefix)
default:
panic(fmt.Errorf("unexpected type of descriptor %q", o))
}
}
// NewPartialDesc returns a descriptor that is used by Collectors that want to
// reserve a specific metric name and type with specific label dimensions of
// which some (but not all) label values might be set already. An invalid
// descriptor is returned in the following cases: The resulting label name
// (assembled from namespace, subsystem, and name) is invalid, the help string
// is empty, unsetLabels is empty or contains invalid label names,
// setLabels (which might be empty) contains invalid label names or label
// values, metricType is not a valid MetricType.
func NewPartialDesc(
namespace, subsystem, name string,
metricType MetricType,
help string,
setLabels Labels,
unsetLabels []string,
) Desc {
return nil // TODO
}
// NewFullDesc returns a descriptor with fully specified name, type, and
// labels. It can be used by Collectors and must be used by Metrics. An invalid
// descriptor is returned if the resulting label name (assembled from namespace,
// subsystem, and name) is invalid, the help string is empty, metricType has an
// invalid value, or the labels contain invalid label names or values. The labels
// might be empty, though.
func NewFullDesc(
namespace, subsystem, name string,
metricType MetricType,
help string,
labels Labels,
) Desc {
return nil // TODO
}
// FullySpecify returns a fully specified descriptor based on the provided
// partial descriptor by setting all the unset labels to the provided values (in
// the same order as they have been specified in the NewFullDesc or DeSpecify
// call). An invalid desc is returned if the provided desc is not a partial
// descriptor, the cardinality of labelValues does not fit, or labelValues
// contains invalid label values.
func FullySpecify(desc Desc, labelValues ...string) Desc {
d, ok := desc.(*partialDesc)
if !ok {
return NewInvalidDesc(fmt.Errorf("tried to fully specify non-partial descriptor %q", desc))
}
return nil // TODO
}
// DeSpecify creates a partial descriptor based on the provided full descriptor
// by adding un-set labels with the provided label names. An invalid desc is
// returned if the provided desc is not a full descriptor, or labelNames
// contains invalid label names (or no label names at all).
func DeSpecify(desc Desc, labelNames ...string) Desc {
d, ok := desc.(*fullDesc)
if !ok {
return NewInvalidDesc(fmt.Errorf("tried to de-specify non-full descriptor %q", desc))
}
if len(ln) == 0 {
return NewInvalidDesc(fmt.Errorf("no label names provided to de-specify %q", desc))
}
for _, ln := range labelNames {
if !validLabelName(ln) {
return NewInvalidDesc(fmt.Errorf("encountered invalid label name %q while de-specifying %q", ln, desc))
}
}
return &partialDesc{*d, labelNames}
}
type fullDesc struct {
fqName, help string
metricType MetricType
setLabels []*dto.LabelPair // Sorted.
}
type partialDesc struct {
fullDesc
unsetLabels []string // Keep in original order.
}
// buildFQName joins the given three name components by "_". Empty name
// components are ignored. If the name parameter itself is empty, an empty
// string is returned, no matter what.
func buildFQName(namespace, subsystem, name string) string {
if name == "" {
return ""
}
switch {
case namespace != "" && subsystem != "":
return namespace + "_" + subsystem + "_" + name
case namespace != "":
return namespace + "_" + name
case subsystem != "":
return subsystem + "_" + name
}
return name
}
func validMetricName(n string) bool {
return metricNameRE.MatchString(n)
}
func validLabelName(l string) bool {
return labelNameRE.MatchString(l) &&
!strings.HasPrefix(l, reservedLabelPrefix)
}
func validLabelValue(l string) bool {
return utf8.ValidString(l)
}
// OLD CODE below.
// Desc is the descriptor used by every Prometheus Metric. It is essentially
// the immutable meta-data of a Metric. The normal Metric implementations
// included in this package manage their Desc under the hood. Users only have to
// deal with Desc if they use advanced features like the ExpvarCollector or
// custom Collectors and Metrics.
//
// Descriptors registered with the same registry have to fulfill certain
// consistency and uniqueness criteria if they share the same fully-qualified
// name: They must have the same help string and the same label names (aka label
// dimensions) in each, constLabels and variableLabels, but they must differ in
// the values of the constLabels.
//
// Descriptors that share the same fully-qualified names and the same label
// values of their constLabels are considered equal.
//
// Use NewDesc to create new Desc instances.
type Desc struct {
// fqName has been built from Namespace, Subsystem, and Name.
fqName string
// help provides some helpful information about this metric.
help string
// constLabelPairs contains precalculated DTO label pairs based on
// the constant labels.
constLabelPairs []*dto.LabelPair
// VariableLabels contains names of labels for which the metric
// maintains variable values.
variableLabels []string
// id is a hash of the values of the ConstLabels and fqName. This
// must be unique among all registered descriptors and can therefore be
// used as an identifier of the descriptor.
id uint64
// dimHash is a hash of the label names (preset and variable) and the
// Help string. Each Desc with the same fqName must have the same
// dimHash.
dimHash uint64
// err is an error that occured during construction. It is reported on
// registration time.
err error
}
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
// and will be reported on registration time. variableLabels and constLabels can
// be nil if no such labels should be set. fqName and help must not be empty.
//
// variableLabels only contain the label names. Their label values are variable
// and therefore not part of the Desc. (They are managed within the Metric.)
//
// For constLabels, the label values are constant. Therefore, they are fully
// specified in the Desc. See the Opts documentation for the implications of
// constant labels.
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
d := &Desc{
fqName: fqName,
help: help,
variableLabels: variableLabels,
}
if help == "" {
d.err = errors.New("empty help string")
return d
}
if !metricNameRE.MatchString(fqName) {
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
return d
}
// labelValues contains the label values of const labels (in order of
// their sorted label names) plus the fqName (at position 0).
labelValues := make([]string, 1, len(constLabels)+1)
labelValues[0] = fqName
labelNames := make([]string, 0, len(constLabels)+len(variableLabels))
labelNameSet := map[string]struct{}{}
// First add only the const label names and sort them...
for labelName := range constLabels {
if !validLabelName(labelName) {
d.err = fmt.Errorf("%q is not a valid label name", labelName)
return d
}
labelNames = append(labelNames, labelName)
labelNameSet[labelName] = struct{}{}
}
sort.Strings(labelNames)
// ... so that we can now add const label values in the order of their names.
for _, labelName := range labelNames {
labelValues = append(labelValues, constLabels[labelName])
}
// Now add the variable label names, but prefix them with something that
// cannot be in a regular label name. That prevents matching the label
// dimension with a different mix between preset and variable labels.
for _, labelName := range variableLabels {
if !validLabelName(labelName) {
d.err = fmt.Errorf("%q is not a valid label name", labelName)
return d
}
labelNames = append(labelNames, "$"+labelName)
labelNameSet[labelName] = struct{}{}
}
if len(labelNames) != len(labelNameSet) {
d.err = errors.New("duplicate label names")
return d
}
vh := hashNew()
for _, val := range labelValues {
vh = hashAdd(vh, val)
vh = hashAddByte(vh, separatorByte)
}
d.id = vh
// Sort labelNames so that order doesn't matter for the hash.
sort.Strings(labelNames)
// Now hash together (in this order) the help string and the sorted
// label names.
lh := hashNew()
lh = hashAdd(lh, help)
lh = hashAddByte(lh, separatorByte)
for _, labelName := range labelNames {
lh = hashAdd(lh, labelName)
lh = hashAddByte(lh, separatorByte)
}
d.dimHash = lh
d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
for n, v := range constLabels {
d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{
Name: proto.String(n),
Value: proto.String(v),
})
}
sort.Sort(LabelPairSorter(d.constLabelPairs))
return d
}
func (d *Desc) String() string {
lpStrings := make([]string, 0, len(d.constLabelPairs))
for _, lp := range d.constLabelPairs {
lpStrings = append(
lpStrings,
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
)
}
return fmt.Sprintf(
"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: %v}",
d.fqName,
d.help,
strings.Join(lpStrings, ","),
d.variableLabels,
)
}