1// Copyright 2014 The Prometheus Authors 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package expfmt 15 16import ( 17 "fmt" 18 "io" 19 "math" 20 "strings" 21 22 dto "github.com/prometheus/client_model/go" 23 "github.com/prometheus/common/model" 24) 25 26// MetricFamilyToText converts a MetricFamily proto message into text format and 27// writes the resulting lines to 'out'. It returns the number of bytes written 28// and any error encountered. The output will have the same order as the input, 29// no further sorting is performed. Furthermore, this function assumes the input 30// is already sanitized and does not perform any sanity checks. If the input 31// contains duplicate metrics or invalid metric or label names, the conversion 32// will result in invalid text format output. 33// 34// This method fulfills the type 'prometheus.encoder'. 35func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { 36 var written int 37 38 // Fail-fast checks. 39 if len(in.Metric) == 0 { 40 return written, fmt.Errorf("MetricFamily has no metrics: %s", in) 41 } 42 name := in.GetName() 43 if name == "" { 44 return written, fmt.Errorf("MetricFamily has no name: %s", in) 45 } 46 47 // Comments, first HELP, then TYPE. 48 if in.Help != nil { 49 n, err := fmt.Fprintf( 50 out, "# HELP %s %s\n", 51 name, escapeString(*in.Help, false), 52 ) 53 written += n 54 if err != nil { 55 return written, err 56 } 57 } 58 metricType := in.GetType() 59 n, err := fmt.Fprintf( 60 out, "# TYPE %s %s\n", 61 name, strings.ToLower(metricType.String()), 62 ) 63 written += n 64 if err != nil { 65 return written, err 66 } 67 68 // Finally the samples, one line for each. 69 for _, metric := range in.Metric { 70 switch metricType { 71 case dto.MetricType_COUNTER: 72 if metric.Counter == nil { 73 return written, fmt.Errorf( 74 "expected counter in metric %s %s", name, metric, 75 ) 76 } 77 n, err = writeSample( 78 name, metric, "", "", 79 metric.Counter.GetValue(), 80 out, 81 ) 82 case dto.MetricType_GAUGE: 83 if metric.Gauge == nil { 84 return written, fmt.Errorf( 85 "expected gauge in metric %s %s", name, metric, 86 ) 87 } 88 n, err = writeSample( 89 name, metric, "", "", 90 metric.Gauge.GetValue(), 91 out, 92 ) 93 case dto.MetricType_UNTYPED: 94 if metric.Untyped == nil { 95 return written, fmt.Errorf( 96 "expected untyped in metric %s %s", name, metric, 97 ) 98 } 99 n, err = writeSample( 100 name, metric, "", "", 101 metric.Untyped.GetValue(), 102 out, 103 ) 104 case dto.MetricType_SUMMARY: 105 if metric.Summary == nil { 106 return written, fmt.Errorf( 107 "expected summary in metric %s %s", name, metric, 108 ) 109 } 110 for _, q := range metric.Summary.Quantile { 111 n, err = writeSample( 112 name, metric, 113 model.QuantileLabel, fmt.Sprint(q.GetQuantile()), 114 q.GetValue(), 115 out, 116 ) 117 written += n 118 if err != nil { 119 return written, err 120 } 121 } 122 n, err = writeSample( 123 name+"_sum", metric, "", "", 124 metric.Summary.GetSampleSum(), 125 out, 126 ) 127 if err != nil { 128 return written, err 129 } 130 written += n 131 n, err = writeSample( 132 name+"_count", metric, "", "", 133 float64(metric.Summary.GetSampleCount()), 134 out, 135 ) 136 case dto.MetricType_HISTOGRAM: 137 if metric.Histogram == nil { 138 return written, fmt.Errorf( 139 "expected histogram in metric %s %s", name, metric, 140 ) 141 } 142 infSeen := false 143 for _, q := range metric.Histogram.Bucket { 144 n, err = writeSample( 145 name+"_bucket", metric, 146 model.BucketLabel, fmt.Sprint(q.GetUpperBound()), 147 float64(q.GetCumulativeCount()), 148 out, 149 ) 150 written += n 151 if err != nil { 152 return written, err 153 } 154 if math.IsInf(q.GetUpperBound(), +1) { 155 infSeen = true 156 } 157 } 158 if !infSeen { 159 n, err = writeSample( 160 name+"_bucket", metric, 161 model.BucketLabel, "+Inf", 162 float64(metric.Histogram.GetSampleCount()), 163 out, 164 ) 165 if err != nil { 166 return written, err 167 } 168 written += n 169 } 170 n, err = writeSample( 171 name+"_sum", metric, "", "", 172 metric.Histogram.GetSampleSum(), 173 out, 174 ) 175 if err != nil { 176 return written, err 177 } 178 written += n 179 n, err = writeSample( 180 name+"_count", metric, "", "", 181 float64(metric.Histogram.GetSampleCount()), 182 out, 183 ) 184 default: 185 return written, fmt.Errorf( 186 "unexpected type in metric %s %s", name, metric, 187 ) 188 } 189 written += n 190 if err != nil { 191 return written, err 192 } 193 } 194 return written, nil 195} 196 197// writeSample writes a single sample in text format to out, given the metric 198// name, the metric proto message itself, optionally an additional label name 199// and value (use empty strings if not required), and the value. The function 200// returns the number of bytes written and any error encountered. 201func writeSample( 202 name string, 203 metric *dto.Metric, 204 additionalLabelName, additionalLabelValue string, 205 value float64, 206 out io.Writer, 207) (int, error) { 208 var written int 209 n, err := fmt.Fprint(out, name) 210 written += n 211 if err != nil { 212 return written, err 213 } 214 n, err = labelPairsToText( 215 metric.Label, 216 additionalLabelName, additionalLabelValue, 217 out, 218 ) 219 written += n 220 if err != nil { 221 return written, err 222 } 223 n, err = fmt.Fprintf(out, " %v", value) 224 written += n 225 if err != nil { 226 return written, err 227 } 228 if metric.TimestampMs != nil { 229 n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs) 230 written += n 231 if err != nil { 232 return written, err 233 } 234 } 235 n, err = out.Write([]byte{'\n'}) 236 written += n 237 if err != nil { 238 return written, err 239 } 240 return written, nil 241} 242 243// labelPairsToText converts a slice of LabelPair proto messages plus the 244// explicitly given additional label pair into text formatted as required by the 245// text format and writes it to 'out'. An empty slice in combination with an 246// empty string 'additionalLabelName' results in nothing being 247// written. Otherwise, the label pairs are written, escaped as required by the 248// text format, and enclosed in '{...}'. The function returns the number of 249// bytes written and any error encountered. 250func labelPairsToText( 251 in []*dto.LabelPair, 252 additionalLabelName, additionalLabelValue string, 253 out io.Writer, 254) (int, error) { 255 if len(in) == 0 && additionalLabelName == "" { 256 return 0, nil 257 } 258 var written int 259 separator := '{' 260 for _, lp := range in { 261 n, err := fmt.Fprintf( 262 out, `%c%s="%s"`, 263 separator, lp.GetName(), escapeString(lp.GetValue(), true), 264 ) 265 written += n 266 if err != nil { 267 return written, err 268 } 269 separator = ',' 270 } 271 if additionalLabelName != "" { 272 n, err := fmt.Fprintf( 273 out, `%c%s="%s"`, 274 separator, additionalLabelName, 275 escapeString(additionalLabelValue, true), 276 ) 277 written += n 278 if err != nil { 279 return written, err 280 } 281 } 282 n, err := out.Write([]byte{'}'}) 283 written += n 284 if err != nil { 285 return written, err 286 } 287 return written, nil 288} 289 290var ( 291 escape = strings.NewReplacer("\\", `\\`, "\n", `\n`) 292 escapeWithDoubleQuote = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`) 293) 294 295// escapeString replaces '\' by '\\', new line character by '\n', and - if 296// includeDoubleQuote is true - '"' by '\"'. 297func escapeString(v string, includeDoubleQuote bool) string { 298 if includeDoubleQuote { 299 return escapeWithDoubleQuote.Replace(v) 300 } 301 302 return escape.Replace(v) 303} 304