mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
7.5 KiB
303 lines
7.5 KiB
// 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) |
|
}
|
|
|