Fix a number of minor things.
- Clarify documentation about sorting requirements. - Add missing histogram support in consistency check. - Add label sorting to consistency check. - Improve error messages when reporting a metric. (Previously, the metric name was not printed.)
This commit is contained in:
parent
f17ca6af6d
commit
59f2c7d8b0
|
@ -121,7 +121,7 @@ func (m Metric) Fingerprint() Fingerprint {
|
||||||
return metricToFingerprint(m)
|
return metricToFingerprint(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fingerprint returns a Metric's Fingerprint calculated by a faster hashing
|
// FastFingerprint returns a Metric's Fingerprint calculated by a faster hashing
|
||||||
// algorithm, which is, however, more susceptible to hash collisions.
|
// algorithm, which is, however, more susceptible to hash collisions.
|
||||||
func (m Metric) FastFingerprint() Fingerprint {
|
func (m Metric) FastFingerprint() Fingerprint {
|
||||||
return metricToFastFingerprint(m)
|
return metricToFastFingerprint(m)
|
||||||
|
|
|
@ -170,6 +170,11 @@ func Unregister(c Collector) bool {
|
||||||
// checks are performed, but no further consistency checks (which would require
|
// checks are performed, but no further consistency checks (which would require
|
||||||
// knowledge of a metric descriptor).
|
// knowledge of a metric descriptor).
|
||||||
//
|
//
|
||||||
|
// Sorting concerns: The caller is responsible for sorting the label pairs in
|
||||||
|
// each metric. However, the order of metrics will be sorted by the registry as
|
||||||
|
// it is required anyway after merging with the metric families collected
|
||||||
|
// conventionally.
|
||||||
|
//
|
||||||
// The function must be callable at any time and concurrently.
|
// The function must be callable at any time and concurrently.
|
||||||
func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
|
func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
|
||||||
defRegistry.metricFamilyInjectionHook = hook
|
defRegistry.metricFamilyInjectionHook = hook
|
||||||
|
@ -520,10 +525,11 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
|
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
|
||||||
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
|
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
|
||||||
metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil ||
|
metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil ||
|
||||||
|
metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
|
||||||
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
|
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"collected metric %q is not a %s",
|
"collected metric %s %s is not a %s",
|
||||||
dtoMetric, metricFamily.Type,
|
metricFamily.GetName(), dtoMetric, metricFamily.GetType(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,6 +539,11 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
buf.WriteString(metricFamily.GetName())
|
buf.WriteString(metricFamily.GetName())
|
||||||
buf.WriteByte(model.SeparatorByte)
|
buf.WriteByte(model.SeparatorByte)
|
||||||
h.Write(buf.Bytes())
|
h.Write(buf.Bytes())
|
||||||
|
// Make sure label pairs are sorted. We depend on it for the consistency
|
||||||
|
// check. Label pairs must be sorted by contract. But the point of this
|
||||||
|
// method is to check for contract violations. So we better do the sort
|
||||||
|
// now.
|
||||||
|
sort.Sort(LabelPairSorter(dtoMetric.Label))
|
||||||
for _, lp := range dtoMetric.Label {
|
for _, lp := range dtoMetric.Label {
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
buf.WriteString(lp.GetValue())
|
buf.WriteString(lp.GetValue())
|
||||||
|
@ -542,8 +553,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
metricHash := h.Sum64()
|
metricHash := h.Sum64()
|
||||||
if _, exists := metricHashes[metricHash]; exists {
|
if _, exists := metricHashes[metricHash]; exists {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"collected metric %q was collected before with the same name and label values",
|
"collected metric %s %s was collected before with the same name and label values",
|
||||||
dtoMetric,
|
metricFamily.GetName(), dtoMetric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
metricHashes[metricHash] = struct{}{}
|
metricHashes[metricHash] = struct{}{}
|
||||||
|
@ -555,14 +566,14 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
// Desc consistency with metric family.
|
// Desc consistency with metric family.
|
||||||
if metricFamily.GetName() != desc.fqName {
|
if metricFamily.GetName() != desc.fqName {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"collected metric %q has name %q but should have %q",
|
"collected metric %s %s has name %q but should have %q",
|
||||||
dtoMetric, metricFamily.GetName(), desc.fqName,
|
metricFamily.GetName(), dtoMetric, metricFamily.GetName(), desc.fqName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if metricFamily.GetHelp() != desc.help {
|
if metricFamily.GetHelp() != desc.help {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"collected metric %q has help %q but should have %q",
|
"collected metric %s %s has help %q but should have %q",
|
||||||
dtoMetric, metricFamily.GetHelp(), desc.help,
|
metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,8 +587,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
}
|
}
|
||||||
if len(lpsFromDesc) != len(dtoMetric.Label) {
|
if len(lpsFromDesc) != len(dtoMetric.Label) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"labels in collected metric %q are inconsistent with descriptor %s",
|
"labels in collected metric %s %s are inconsistent with descriptor %s",
|
||||||
dtoMetric, desc,
|
metricFamily.GetName(), dtoMetric, desc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sort.Sort(LabelPairSorter(lpsFromDesc))
|
sort.Sort(LabelPairSorter(lpsFromDesc))
|
||||||
|
@ -586,8 +597,8 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
||||||
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
|
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"labels in collected metric %q are inconsistent with descriptor %s",
|
"labels in collected metric %s %s are inconsistent with descriptor %s",
|
||||||
dtoMetric, desc,
|
metricFamily.GetName(), dtoMetric, desc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -597,7 +608,10 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
|
||||||
|
|
||||||
// Is the desc registered?
|
// Is the desc registered?
|
||||||
if _, exist := r.descIDs[desc.id]; !exist {
|
if _, exist := r.descIDs[desc.id]; !exist {
|
||||||
return fmt.Errorf("collected metric %q with unregistered descriptor %s", dtoMetric, desc)
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s with unregistered descriptor %s",
|
||||||
|
metricFamily.GetName(), dtoMetric, desc,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -68,14 +68,14 @@ func testHandler(t testing.TB) {
|
||||||
Metric: []*dto.Metric{
|
Metric: []*dto.Metric{
|
||||||
{
|
{
|
||||||
Label: []*dto.LabelPair{
|
Label: []*dto.LabelPair{
|
||||||
{
|
|
||||||
Name: proto.String("externallabelname"),
|
|
||||||
Value: proto.String("externalval1"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: proto.String("externalconstname"),
|
Name: proto.String("externalconstname"),
|
||||||
Value: proto.String("externalconstvalue"),
|
Value: proto.String("externalconstvalue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: proto.String("externallabelname"),
|
||||||
|
Value: proto.String("externalval1"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Counter: &dto.Counter{
|
Counter: &dto.Counter{
|
||||||
Value: proto.Float64(1),
|
Value: proto.Float64(1),
|
||||||
|
@ -100,27 +100,27 @@ func testHandler(t testing.TB) {
|
||||||
externalMetricFamilyAsBytes := externalBuf.Bytes()
|
externalMetricFamilyAsBytes := externalBuf.Bytes()
|
||||||
externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring
|
externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring
|
||||||
# TYPE externalname counter
|
# TYPE externalname counter
|
||||||
externalname{externallabelname="externalval1",externalconstname="externalconstvalue"} 1
|
externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1
|
||||||
`)
|
`)
|
||||||
externalMetricFamilyAsProtoText := []byte(`name: "externalname"
|
externalMetricFamilyAsProtoText := []byte(`name: "externalname"
|
||||||
help: "externaldocstring"
|
help: "externaldocstring"
|
||||||
type: COUNTER
|
type: COUNTER
|
||||||
metric: <
|
metric: <
|
||||||
label: <
|
|
||||||
name: "externallabelname"
|
|
||||||
value: "externalval1"
|
|
||||||
>
|
|
||||||
label: <
|
label: <
|
||||||
name: "externalconstname"
|
name: "externalconstname"
|
||||||
value: "externalconstvalue"
|
value: "externalconstvalue"
|
||||||
>
|
>
|
||||||
|
label: <
|
||||||
|
name: "externallabelname"
|
||||||
|
value: "externalval1"
|
||||||
|
>
|
||||||
counter: <
|
counter: <
|
||||||
value: 1
|
value: 1
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
|
|
||||||
`)
|
`)
|
||||||
externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externallabelname" value:"externalval1" > label:<name:"externalconstname" value:"externalconstvalue" > counter:<value:1 > >
|
externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externalconstname" value:"externalconstvalue" > label:<name:"externallabelname" value:"externalval1" > counter:<value:1 > >
|
||||||
`)
|
`)
|
||||||
|
|
||||||
expectedMetricFamily := &dto.MetricFamily{
|
expectedMetricFamily := &dto.MetricFamily{
|
||||||
|
|
|
@ -79,7 +79,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
case dto.MetricType_COUNTER:
|
case dto.MetricType_COUNTER:
|
||||||
if metric.Counter == nil {
|
if metric.Counter == nil {
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
"expected counter in metric %s", metric,
|
"expected counter in metric %s %s", name, metric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
|
@ -90,7 +90,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
case dto.MetricType_GAUGE:
|
case dto.MetricType_GAUGE:
|
||||||
if metric.Gauge == nil {
|
if metric.Gauge == nil {
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
"expected gauge in metric %s", metric,
|
"expected gauge in metric %s %s", name, metric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
|
@ -101,7 +101,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
case dto.MetricType_UNTYPED:
|
case dto.MetricType_UNTYPED:
|
||||||
if metric.Untyped == nil {
|
if metric.Untyped == nil {
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
"expected untyped in metric %s", metric,
|
"expected untyped in metric %s %s", name, metric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
|
@ -112,7 +112,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
case dto.MetricType_SUMMARY:
|
case dto.MetricType_SUMMARY:
|
||||||
if metric.Summary == nil {
|
if metric.Summary == nil {
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
"expected summary in metric %s", metric,
|
"expected summary in metric %s %s", name, metric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, q := range metric.Summary.Quantile {
|
for _, q := range metric.Summary.Quantile {
|
||||||
|
@ -144,7 +144,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
case dto.MetricType_HISTOGRAM:
|
case dto.MetricType_HISTOGRAM:
|
||||||
if metric.Histogram == nil {
|
if metric.Histogram == nil {
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
"expected summary in metric %s", metric,
|
"expected histogram in metric %s %s", name, metric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
infSeen := false
|
infSeen := false
|
||||||
|
@ -191,7 +191,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
"unexpected type in metric %s", metric,
|
"unexpected type in metric %s %s", name, metric,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
written += n
|
written += n
|
||||||
|
|
|
@ -83,10 +83,17 @@ type Parser struct {
|
||||||
// and exactly the same label set), the resulting MetricFamily will contain
|
// and exactly the same label set), the resulting MetricFamily will contain
|
||||||
// duplicate Metric proto messages. Similar is true for duplicate label
|
// duplicate Metric proto messages. Similar is true for duplicate label
|
||||||
// names. Checks for duplicates have to be performed separately, if required.
|
// names. Checks for duplicates have to be performed separately, if required.
|
||||||
|
// Also note that neither the metrics within each MetricFamily are sorted nor
|
||||||
|
// the label pairs within each Metric. Sorting is not required for the most
|
||||||
|
// frequent use of this method, which is sample ingestion in the Prometheus
|
||||||
|
// server. However, for presentation purposes, you might want to sort the
|
||||||
|
// metrics, and in some cases, you must sort the labels, e.g. for consumption by
|
||||||
|
// the metric family injection hook of the Prometheus registry.
|
||||||
//
|
//
|
||||||
// Summaries are a rather special beast. You would probably not use them in the
|
// Summaries and histograms are rather special beasts. You would probably not
|
||||||
// simple text format anyway. This method can deal with summaries if they are
|
// use them in the simple text format anyway. This method can deal with
|
||||||
// presented in exactly the way the text.Create function creates them.
|
// summaries and histograms if they are presented in exactly the way the
|
||||||
|
// text.Create function creates them.
|
||||||
//
|
//
|
||||||
// This method must not be called concurrently. If you want to parse different
|
// This method must not be called concurrently. If you want to parse different
|
||||||
// input concurrently, instantiate a separate Parser for each goroutine.
|
// input concurrently, instantiate a separate Parser for each goroutine.
|
||||||
|
|
Loading…
Reference in New Issue