mirror of
https://github.com/rancher/norman.git
synced 2025-08-28 11:50:34 +00:00
To enhance operatability for the service using norman framework, It's better to expose internal state as detail as possible. This is the just starting point but at least which handler is executed often and which handler with which key's execution is often failed metrics is very useful to spot the place operator have to dig in when something happened. So this commit added 2 metrics. 1: handler execution total count 2: handler execution failure total count
304 lines
7.5 KiB
Go
304 lines
7.5 KiB
Go
// 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 expfmt
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
|
|
dto "github.com/prometheus/client_model/go"
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
// MetricFamilyToText converts a MetricFamily proto message into text format and
|
|
// writes the resulting lines to 'out'. It returns the number of bytes written
|
|
// and any error encountered. The output will have the same order as the input,
|
|
// no further sorting is performed. Furthermore, this function assumes the input
|
|
// is already sanitized and does not perform any sanity checks. If the input
|
|
// contains duplicate metrics or invalid metric or label names, the conversion
|
|
// will result in invalid text format output.
|
|
//
|
|
// This method fulfills the type 'prometheus.encoder'.
|
|
func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
|
var written int
|
|
|
|
// Fail-fast checks.
|
|
if len(in.Metric) == 0 {
|
|
return written, fmt.Errorf("MetricFamily has no metrics: %s", in)
|
|
}
|
|
name := in.GetName()
|
|
if name == "" {
|
|
return written, fmt.Errorf("MetricFamily has no name: %s", in)
|
|
}
|
|
|
|
// Comments, first HELP, then TYPE.
|
|
if in.Help != nil {
|
|
n, err := fmt.Fprintf(
|
|
out, "# HELP %s %s\n",
|
|
name, escapeString(*in.Help, false),
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
}
|
|
metricType := in.GetType()
|
|
n, err := fmt.Fprintf(
|
|
out, "# TYPE %s %s\n",
|
|
name, strings.ToLower(metricType.String()),
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
|
|
// Finally the samples, one line for each.
|
|
for _, metric := range in.Metric {
|
|
switch metricType {
|
|
case dto.MetricType_COUNTER:
|
|
if metric.Counter == nil {
|
|
return written, fmt.Errorf(
|
|
"expected counter in metric %s %s", name, metric,
|
|
)
|
|
}
|
|
n, err = writeSample(
|
|
name, metric, "", "",
|
|
metric.Counter.GetValue(),
|
|
out,
|
|
)
|
|
case dto.MetricType_GAUGE:
|
|
if metric.Gauge == nil {
|
|
return written, fmt.Errorf(
|
|
"expected gauge in metric %s %s", name, metric,
|
|
)
|
|
}
|
|
n, err = writeSample(
|
|
name, metric, "", "",
|
|
metric.Gauge.GetValue(),
|
|
out,
|
|
)
|
|
case dto.MetricType_UNTYPED:
|
|
if metric.Untyped == nil {
|
|
return written, fmt.Errorf(
|
|
"expected untyped in metric %s %s", name, metric,
|
|
)
|
|
}
|
|
n, err = writeSample(
|
|
name, metric, "", "",
|
|
metric.Untyped.GetValue(),
|
|
out,
|
|
)
|
|
case dto.MetricType_SUMMARY:
|
|
if metric.Summary == nil {
|
|
return written, fmt.Errorf(
|
|
"expected summary in metric %s %s", name, metric,
|
|
)
|
|
}
|
|
for _, q := range metric.Summary.Quantile {
|
|
n, err = writeSample(
|
|
name, metric,
|
|
model.QuantileLabel, fmt.Sprint(q.GetQuantile()),
|
|
q.GetValue(),
|
|
out,
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
}
|
|
n, err = writeSample(
|
|
name+"_sum", metric, "", "",
|
|
metric.Summary.GetSampleSum(),
|
|
out,
|
|
)
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
written += n
|
|
n, err = writeSample(
|
|
name+"_count", metric, "", "",
|
|
float64(metric.Summary.GetSampleCount()),
|
|
out,
|
|
)
|
|
case dto.MetricType_HISTOGRAM:
|
|
if metric.Histogram == nil {
|
|
return written, fmt.Errorf(
|
|
"expected histogram in metric %s %s", name, metric,
|
|
)
|
|
}
|
|
infSeen := false
|
|
for _, q := range metric.Histogram.Bucket {
|
|
n, err = writeSample(
|
|
name+"_bucket", metric,
|
|
model.BucketLabel, fmt.Sprint(q.GetUpperBound()),
|
|
float64(q.GetCumulativeCount()),
|
|
out,
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
if math.IsInf(q.GetUpperBound(), +1) {
|
|
infSeen = true
|
|
}
|
|
}
|
|
if !infSeen {
|
|
n, err = writeSample(
|
|
name+"_bucket", metric,
|
|
model.BucketLabel, "+Inf",
|
|
float64(metric.Histogram.GetSampleCount()),
|
|
out,
|
|
)
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
written += n
|
|
}
|
|
n, err = writeSample(
|
|
name+"_sum", metric, "", "",
|
|
metric.Histogram.GetSampleSum(),
|
|
out,
|
|
)
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
written += n
|
|
n, err = writeSample(
|
|
name+"_count", metric, "", "",
|
|
float64(metric.Histogram.GetSampleCount()),
|
|
out,
|
|
)
|
|
default:
|
|
return written, fmt.Errorf(
|
|
"unexpected type in metric %s %s", name, metric,
|
|
)
|
|
}
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
// writeSample writes a single sample in text format to out, given the metric
|
|
// name, the metric proto message itself, optionally an additional label name
|
|
// and value (use empty strings if not required), and the value. The function
|
|
// returns the number of bytes written and any error encountered.
|
|
func writeSample(
|
|
name string,
|
|
metric *dto.Metric,
|
|
additionalLabelName, additionalLabelValue string,
|
|
value float64,
|
|
out io.Writer,
|
|
) (int, error) {
|
|
var written int
|
|
n, err := fmt.Fprint(out, name)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
n, err = labelPairsToText(
|
|
metric.Label,
|
|
additionalLabelName, additionalLabelValue,
|
|
out,
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
n, err = fmt.Fprintf(out, " %v", value)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
if metric.TimestampMs != nil {
|
|
n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
}
|
|
n, err = out.Write([]byte{'\n'})
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
// labelPairsToText converts a slice of LabelPair proto messages plus the
|
|
// explicitly given additional label pair into text formatted as required by the
|
|
// text format and writes it to 'out'. An empty slice in combination with an
|
|
// empty string 'additionalLabelName' results in nothing being
|
|
// written. Otherwise, the label pairs are written, escaped as required by the
|
|
// text format, and enclosed in '{...}'. The function returns the number of
|
|
// bytes written and any error encountered.
|
|
func labelPairsToText(
|
|
in []*dto.LabelPair,
|
|
additionalLabelName, additionalLabelValue string,
|
|
out io.Writer,
|
|
) (int, error) {
|
|
if len(in) == 0 && additionalLabelName == "" {
|
|
return 0, nil
|
|
}
|
|
var written int
|
|
separator := '{'
|
|
for _, lp := range in {
|
|
n, err := fmt.Fprintf(
|
|
out, `%c%s="%s"`,
|
|
separator, lp.GetName(), escapeString(lp.GetValue(), true),
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
separator = ','
|
|
}
|
|
if additionalLabelName != "" {
|
|
n, err := fmt.Fprintf(
|
|
out, `%c%s="%s"`,
|
|
separator, additionalLabelName,
|
|
escapeString(additionalLabelValue, true),
|
|
)
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
}
|
|
n, err := out.Write([]byte{'}'})
|
|
written += n
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
return written, nil
|
|
}
|
|
|
|
var (
|
|
escape = strings.NewReplacer("\\", `\\`, "\n", `\n`)
|
|
escapeWithDoubleQuote = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
|
|
)
|
|
|
|
// escapeString replaces '\' by '\\', new line character by '\n', and - if
|
|
// includeDoubleQuote is true - '"' by '\"'.
|
|
func escapeString(v string, includeDoubleQuote bool) string {
|
|
if includeDoubleQuote {
|
|
return escapeWithDoubleQuote.Replace(v)
|
|
}
|
|
|
|
return escape.Replace(v)
|
|
}
|