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