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