gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/prometheus/common/expfmt/text_create.go (about) 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 14 package expfmt 15 16 import ( 17 "bufio" 18 "fmt" 19 "io" 20 "io/ioutil" 21 "math" 22 "strconv" 23 "strings" 24 "sync" 25 26 "github.com/prometheus/common/model" 27 28 dto "github.com/prometheus/client_model/go" 29 ) 30 31 // enhancedWriter has all the enhanced write functions needed here. bufio.Writer 32 // implements it. 33 type enhancedWriter interface { 34 io.Writer 35 WriteRune(r rune) (n int, err error) 36 WriteString(s string) (n int, err error) 37 WriteByte(c byte) error 38 } 39 40 const ( 41 initialNumBufSize = 24 42 ) 43 44 var ( 45 bufPool = sync.Pool{ 46 New: func() interface{} { 47 return bufio.NewWriter(ioutil.Discard) 48 }, 49 } 50 numBufPool = sync.Pool{ 51 New: func() interface{} { 52 b := make([]byte, 0, initialNumBufSize) 53 return &b 54 }, 55 } 56 ) 57 58 // MetricFamilyToText converts a MetricFamily proto message into text format and 59 // writes the resulting lines to 'out'. It returns the number of bytes written 60 // and any error encountered. The output will have the same order as the input, 61 // no further sorting is performed. Furthermore, this function assumes the input 62 // is already sanitized and does not perform any sanity checks. If the input 63 // contains duplicate metrics or invalid metric or label names, the conversion 64 // will result in invalid text format output. 65 // 66 // This method fulfills the type 'prometheus.encoder'. 67 func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) { 68 // Fail-fast checks. 69 if len(in.Metric) == 0 { 70 return 0, fmt.Errorf("MetricFamily has no metrics: %s", in) 71 } 72 name := in.GetName() 73 if name == "" { 74 return 0, fmt.Errorf("MetricFamily has no name: %s", in) 75 } 76 77 // Try the interface upgrade. If it doesn't work, we'll use a 78 // bufio.Writer from the sync.Pool. 79 w, ok := out.(enhancedWriter) 80 if !ok { 81 b := bufPool.Get().(*bufio.Writer) 82 b.Reset(out) 83 w = b 84 defer func() { 85 bErr := b.Flush() 86 if err == nil { 87 err = bErr 88 } 89 bufPool.Put(b) 90 }() 91 } 92 93 var n int 94 95 // Comments, first HELP, then TYPE. 96 if in.Help != nil { 97 n, err = w.WriteString("# HELP ") 98 written += n 99 if err != nil { 100 return 101 } 102 n, err = w.WriteString(name) 103 written += n 104 if err != nil { 105 return 106 } 107 err = w.WriteByte(' ') 108 written++ 109 if err != nil { 110 return 111 } 112 n, err = writeEscapedString(w, *in.Help, false) 113 written += n 114 if err != nil { 115 return 116 } 117 err = w.WriteByte('\n') 118 written++ 119 if err != nil { 120 return 121 } 122 } 123 n, err = w.WriteString("# TYPE ") 124 written += n 125 if err != nil { 126 return 127 } 128 n, err = w.WriteString(name) 129 written += n 130 if err != nil { 131 return 132 } 133 metricType := in.GetType() 134 switch metricType { 135 case dto.MetricType_COUNTER: 136 n, err = w.WriteString(" counter\n") 137 case dto.MetricType_GAUGE: 138 n, err = w.WriteString(" gauge\n") 139 case dto.MetricType_SUMMARY: 140 n, err = w.WriteString(" summary\n") 141 case dto.MetricType_UNTYPED: 142 n, err = w.WriteString(" untyped\n") 143 case dto.MetricType_HISTOGRAM: 144 n, err = w.WriteString(" histogram\n") 145 default: 146 return written, fmt.Errorf("unknown metric type %s", metricType.String()) 147 } 148 written += n 149 if err != nil { 150 return 151 } 152 153 // Finally the samples, one line for each. 154 for _, metric := range in.Metric { 155 switch metricType { 156 case dto.MetricType_COUNTER: 157 if metric.Counter == nil { 158 return written, fmt.Errorf( 159 "expected counter in metric %s %s", name, metric, 160 ) 161 } 162 n, err = writeSample( 163 w, name, "", metric, "", 0, 164 metric.Counter.GetValue(), 165 ) 166 case dto.MetricType_GAUGE: 167 if metric.Gauge == nil { 168 return written, fmt.Errorf( 169 "expected gauge in metric %s %s", name, metric, 170 ) 171 } 172 n, err = writeSample( 173 w, name, "", metric, "", 0, 174 metric.Gauge.GetValue(), 175 ) 176 case dto.MetricType_UNTYPED: 177 if metric.Untyped == nil { 178 return written, fmt.Errorf( 179 "expected untyped in metric %s %s", name, metric, 180 ) 181 } 182 n, err = writeSample( 183 w, name, "", metric, "", 0, 184 metric.Untyped.GetValue(), 185 ) 186 case dto.MetricType_SUMMARY: 187 if metric.Summary == nil { 188 return written, fmt.Errorf( 189 "expected summary in metric %s %s", name, metric, 190 ) 191 } 192 for _, q := range metric.Summary.Quantile { 193 n, err = writeSample( 194 w, name, "", metric, 195 model.QuantileLabel, q.GetQuantile(), 196 q.GetValue(), 197 ) 198 written += n 199 if err != nil { 200 return 201 } 202 } 203 n, err = writeSample( 204 w, name, "_sum", metric, "", 0, 205 metric.Summary.GetSampleSum(), 206 ) 207 written += n 208 if err != nil { 209 return 210 } 211 n, err = writeSample( 212 w, name, "_count", metric, "", 0, 213 float64(metric.Summary.GetSampleCount()), 214 ) 215 case dto.MetricType_HISTOGRAM: 216 if metric.Histogram == nil { 217 return written, fmt.Errorf( 218 "expected histogram in metric %s %s", name, metric, 219 ) 220 } 221 infSeen := false 222 for _, b := range metric.Histogram.Bucket { 223 n, err = writeSample( 224 w, name, "_bucket", metric, 225 model.BucketLabel, b.GetUpperBound(), 226 float64(b.GetCumulativeCount()), 227 ) 228 written += n 229 if err != nil { 230 return 231 } 232 if math.IsInf(b.GetUpperBound(), +1) { 233 infSeen = true 234 } 235 } 236 if !infSeen { 237 n, err = writeSample( 238 w, name, "_bucket", metric, 239 model.BucketLabel, math.Inf(+1), 240 float64(metric.Histogram.GetSampleCount()), 241 ) 242 written += n 243 if err != nil { 244 return 245 } 246 } 247 n, err = writeSample( 248 w, name, "_sum", metric, "", 0, 249 metric.Histogram.GetSampleSum(), 250 ) 251 written += n 252 if err != nil { 253 return 254 } 255 n, err = writeSample( 256 w, name, "_count", metric, "", 0, 257 float64(metric.Histogram.GetSampleCount()), 258 ) 259 default: 260 return written, fmt.Errorf( 261 "unexpected type in metric %s %s", name, metric, 262 ) 263 } 264 written += n 265 if err != nil { 266 return 267 } 268 } 269 return 270 } 271 272 // writeSample writes a single sample in text format to w, given the metric 273 // name, the metric proto message itself, optionally an additional label name 274 // with a float64 value (use empty string as label name if not required), and 275 // the value. The function returns the number of bytes written and any error 276 // encountered. 277 func writeSample( 278 w enhancedWriter, 279 name, suffix string, 280 metric *dto.Metric, 281 additionalLabelName string, additionalLabelValue float64, 282 value float64, 283 ) (int, error) { 284 var written int 285 n, err := w.WriteString(name) 286 written += n 287 if err != nil { 288 return written, err 289 } 290 if suffix != "" { 291 n, err = w.WriteString(suffix) 292 written += n 293 if err != nil { 294 return written, err 295 } 296 } 297 n, err = writeLabelPairs( 298 w, metric.Label, additionalLabelName, additionalLabelValue, 299 ) 300 written += n 301 if err != nil { 302 return written, err 303 } 304 err = w.WriteByte(' ') 305 written++ 306 if err != nil { 307 return written, err 308 } 309 n, err = writeFloat(w, value) 310 written += n 311 if err != nil { 312 return written, err 313 } 314 if metric.TimestampMs != nil { 315 err = w.WriteByte(' ') 316 written++ 317 if err != nil { 318 return written, err 319 } 320 n, err = writeInt(w, *metric.TimestampMs) 321 written += n 322 if err != nil { 323 return written, err 324 } 325 } 326 err = w.WriteByte('\n') 327 written++ 328 if err != nil { 329 return written, err 330 } 331 return written, nil 332 } 333 334 // writeLabelPairs converts a slice of LabelPair proto messages plus the 335 // explicitly given additional label pair into text formatted as required by the 336 // text format and writes it to 'w'. An empty slice in combination with an empty 337 // string 'additionalLabelName' results in nothing being written. Otherwise, the 338 // label pairs are written, escaped as required by the text format, and enclosed 339 // in '{...}'. The function returns the number of bytes written and any error 340 // encountered. 341 func writeLabelPairs( 342 w enhancedWriter, 343 in []*dto.LabelPair, 344 additionalLabelName string, additionalLabelValue float64, 345 ) (int, error) { 346 if len(in) == 0 && additionalLabelName == "" { 347 return 0, nil 348 } 349 var ( 350 written int 351 separator byte = '{' 352 ) 353 for _, lp := range in { 354 err := w.WriteByte(separator) 355 written++ 356 if err != nil { 357 return written, err 358 } 359 n, err := w.WriteString(lp.GetName()) 360 written += n 361 if err != nil { 362 return written, err 363 } 364 n, err = w.WriteString(`="`) 365 written += n 366 if err != nil { 367 return written, err 368 } 369 n, err = writeEscapedString(w, lp.GetValue(), true) 370 written += n 371 if err != nil { 372 return written, err 373 } 374 err = w.WriteByte('"') 375 written++ 376 if err != nil { 377 return written, err 378 } 379 separator = ',' 380 } 381 if additionalLabelName != "" { 382 err := w.WriteByte(separator) 383 written++ 384 if err != nil { 385 return written, err 386 } 387 n, err := w.WriteString(additionalLabelName) 388 written += n 389 if err != nil { 390 return written, err 391 } 392 n, err = w.WriteString(`="`) 393 written += n 394 if err != nil { 395 return written, err 396 } 397 n, err = writeFloat(w, additionalLabelValue) 398 written += n 399 if err != nil { 400 return written, err 401 } 402 err = w.WriteByte('"') 403 written++ 404 if err != nil { 405 return written, err 406 } 407 } 408 err := w.WriteByte('}') 409 written++ 410 if err != nil { 411 return written, err 412 } 413 return written, nil 414 } 415 416 // writeEscapedString replaces '\' by '\\', new line character by '\n', and - if 417 // includeDoubleQuote is true - '"' by '\"'. 418 var ( 419 escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`) 420 quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`) 421 ) 422 423 func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) { 424 if includeDoubleQuote { 425 return quotedEscaper.WriteString(w, v) 426 } 427 return escaper.WriteString(w, v) 428 } 429 430 // writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes 431 // a few common cases for increased efficiency. For non-hardcoded cases, it uses 432 // strconv.AppendFloat to avoid allocations, similar to writeInt. 433 func writeFloat(w enhancedWriter, f float64) (int, error) { 434 switch { 435 case f == 1: 436 return 1, w.WriteByte('1') 437 case f == 0: 438 return 1, w.WriteByte('0') 439 case f == -1: 440 return w.WriteString("-1") 441 case math.IsNaN(f): 442 return w.WriteString("NaN") 443 case math.IsInf(f, +1): 444 return w.WriteString("+Inf") 445 case math.IsInf(f, -1): 446 return w.WriteString("-Inf") 447 default: 448 bp := numBufPool.Get().(*[]byte) 449 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64) 450 written, err := w.Write(*bp) 451 numBufPool.Put(bp) 452 return written, err 453 } 454 } 455 456 // writeInt is equivalent to fmt.Fprint with an int64 argument but uses 457 // strconv.AppendInt with a byte slice taken from a sync.Pool to avoid 458 // allocations. 459 func writeInt(w enhancedWriter, i int64) (int, error) { 460 bp := numBufPool.Get().(*[]byte) 461 *bp = strconv.AppendInt((*bp)[:0], i, 10) 462 written, err := w.Write(*bp) 463 numBufPool.Put(bp) 464 return written, err 465 }