github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/prometheus/common/expfmt/openmetrics_create.go (about) 1 // Copyright 2020 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 "bytes" 19 "fmt" 20 "io" 21 "math" 22 "strconv" 23 "strings" 24 25 "github.com/prometheus/common/model" 26 27 dto "github.com/prometheus/client_model/go" 28 ) 29 30 // MetricFamilyToOpenMetrics converts a MetricFamily proto message into the 31 // OpenMetrics text format and writes the resulting lines to 'out'. It returns 32 // the number of bytes written and any error encountered. The output will have 33 // the same order as the input, no further sorting is performed. Furthermore, 34 // this function assumes the input is already sanitized and does not perform any 35 // sanity checks. If the input contains duplicate metrics or invalid metric or 36 // label names, the conversion will result in invalid text format output. 37 // 38 // This function fulfills the type 'expfmt.encoder'. 39 // 40 // Note that OpenMetrics requires a final `# EOF` line. Since this function acts 41 // on individual metric families, it is the responsibility of the caller to 42 // append this line to 'out' once all metric families have been written. 43 // Conveniently, this can be done by calling FinalizeOpenMetrics. 44 // 45 // The output should be fully OpenMetrics compliant. However, there are a few 46 // missing features and peculiarities to avoid complications when switching from 47 // Prometheus to OpenMetrics or vice versa: 48 // 49 // - Counters are expected to have the `_total` suffix in their metric name. In 50 // the output, the suffix will be truncated from the `# TYPE` and `# HELP` 51 // line. A counter with a missing `_total` suffix is not an error. However, 52 // its type will be set to `unknown` in that case to avoid invalid OpenMetrics 53 // output. 54 // 55 // - No support for the following (optional) features: `# UNIT` line, `_created` 56 // line, info type, stateset type, gaugehistogram type. 57 // 58 // - The size of exemplar labels is not checked (i.e. it's possible to create 59 // exemplars that are larger than allowed by the OpenMetrics specification). 60 // 61 // - The value of Counters is not checked. (OpenMetrics doesn't allow counters 62 // with a `NaN` value.) 63 func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) { 64 name := in.GetName() 65 if name == "" { 66 return 0, fmt.Errorf("MetricFamily has no name: %s", in) 67 } 68 69 // Try the interface upgrade. If it doesn't work, we'll use a 70 // bufio.Writer from the sync.Pool. 71 w, ok := out.(enhancedWriter) 72 if !ok { 73 b := bufPool.Get().(*bufio.Writer) 74 b.Reset(out) 75 w = b 76 defer func() { 77 bErr := b.Flush() 78 if err == nil { 79 err = bErr 80 } 81 bufPool.Put(b) 82 }() 83 } 84 85 var ( 86 n int 87 metricType = in.GetType() 88 shortName = name 89 ) 90 if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") { 91 shortName = name[:len(name)-6] 92 } 93 94 // Comments, first HELP, then TYPE. 95 if in.Help != nil { 96 n, err = w.WriteString("# HELP ") 97 written += n 98 if err != nil { 99 return 100 } 101 n, err = w.WriteString(shortName) 102 written += n 103 if err != nil { 104 return 105 } 106 err = w.WriteByte(' ') 107 written++ 108 if err != nil { 109 return 110 } 111 n, err = writeEscapedString(w, *in.Help, true) 112 written += n 113 if err != nil { 114 return 115 } 116 err = w.WriteByte('\n') 117 written++ 118 if err != nil { 119 return 120 } 121 } 122 n, err = w.WriteString("# TYPE ") 123 written += n 124 if err != nil { 125 return 126 } 127 n, err = w.WriteString(shortName) 128 written += n 129 if err != nil { 130 return 131 } 132 switch metricType { 133 case dto.MetricType_COUNTER: 134 if strings.HasSuffix(name, "_total") { 135 n, err = w.WriteString(" counter\n") 136 } else { 137 n, err = w.WriteString(" unknown\n") 138 } 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(" unknown\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 // Note that we have ensured above that either the name 165 // ends on `_total` or that the rendered type is 166 // `unknown`. Therefore, no `_total` must be added here. 167 n, err = writeOpenMetricsSample( 168 w, name, "", metric, "", 0, 169 metric.Counter.GetValue(), 0, false, 170 metric.Counter.Exemplar, 171 ) 172 case dto.MetricType_GAUGE: 173 if metric.Gauge == nil { 174 return written, fmt.Errorf( 175 "expected gauge in metric %s %s", name, metric, 176 ) 177 } 178 n, err = writeOpenMetricsSample( 179 w, name, "", metric, "", 0, 180 metric.Gauge.GetValue(), 0, false, 181 nil, 182 ) 183 case dto.MetricType_UNTYPED: 184 if metric.Untyped == nil { 185 return written, fmt.Errorf( 186 "expected untyped in metric %s %s", name, metric, 187 ) 188 } 189 n, err = writeOpenMetricsSample( 190 w, name, "", metric, "", 0, 191 metric.Untyped.GetValue(), 0, false, 192 nil, 193 ) 194 case dto.MetricType_SUMMARY: 195 if metric.Summary == nil { 196 return written, fmt.Errorf( 197 "expected summary in metric %s %s", name, metric, 198 ) 199 } 200 for _, q := range metric.Summary.Quantile { 201 n, err = writeOpenMetricsSample( 202 w, name, "", metric, 203 model.QuantileLabel, q.GetQuantile(), 204 q.GetValue(), 0, false, 205 nil, 206 ) 207 written += n 208 if err != nil { 209 return 210 } 211 } 212 n, err = writeOpenMetricsSample( 213 w, name, "_sum", metric, "", 0, 214 metric.Summary.GetSampleSum(), 0, false, 215 nil, 216 ) 217 written += n 218 if err != nil { 219 return 220 } 221 n, err = writeOpenMetricsSample( 222 w, name, "_count", metric, "", 0, 223 0, metric.Summary.GetSampleCount(), true, 224 nil, 225 ) 226 case dto.MetricType_HISTOGRAM: 227 if metric.Histogram == nil { 228 return written, fmt.Errorf( 229 "expected histogram in metric %s %s", name, metric, 230 ) 231 } 232 infSeen := false 233 for _, b := range metric.Histogram.Bucket { 234 n, err = writeOpenMetricsSample( 235 w, name, "_bucket", metric, 236 model.BucketLabel, b.GetUpperBound(), 237 0, b.GetCumulativeCount(), true, 238 b.Exemplar, 239 ) 240 written += n 241 if err != nil { 242 return 243 } 244 if math.IsInf(b.GetUpperBound(), +1) { 245 infSeen = true 246 } 247 } 248 if !infSeen { 249 n, err = writeOpenMetricsSample( 250 w, name, "_bucket", metric, 251 model.BucketLabel, math.Inf(+1), 252 0, metric.Histogram.GetSampleCount(), true, 253 nil, 254 ) 255 written += n 256 if err != nil { 257 return 258 } 259 } 260 n, err = writeOpenMetricsSample( 261 w, name, "_sum", metric, "", 0, 262 metric.Histogram.GetSampleSum(), 0, false, 263 nil, 264 ) 265 written += n 266 if err != nil { 267 return 268 } 269 n, err = writeOpenMetricsSample( 270 w, name, "_count", metric, "", 0, 271 0, metric.Histogram.GetSampleCount(), true, 272 nil, 273 ) 274 default: 275 return written, fmt.Errorf( 276 "unexpected type in metric %s %s", name, metric, 277 ) 278 } 279 written += n 280 if err != nil { 281 return 282 } 283 } 284 return 285 } 286 287 // FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics. 288 func FinalizeOpenMetrics(w io.Writer) (written int, err error) { 289 return w.Write([]byte("# EOF\n")) 290 } 291 292 // writeOpenMetricsSample writes a single sample in OpenMetrics text format to 293 // w, given the metric name, the metric proto message itself, optionally an 294 // additional label name with a float64 value (use empty string as label name if 295 // not required), the value (optionally as float64 or uint64, determined by 296 // useIntValue), and optionally an exemplar (use nil if not required). The 297 // function returns the number of bytes written and any error encountered. 298 func writeOpenMetricsSample( 299 w enhancedWriter, 300 name, suffix string, 301 metric *dto.Metric, 302 additionalLabelName string, additionalLabelValue float64, 303 floatValue float64, intValue uint64, useIntValue bool, 304 exemplar *dto.Exemplar, 305 ) (int, error) { 306 var written int 307 n, err := w.WriteString(name) 308 written += n 309 if err != nil { 310 return written, err 311 } 312 if suffix != "" { 313 n, err = w.WriteString(suffix) 314 written += n 315 if err != nil { 316 return written, err 317 } 318 } 319 n, err = writeOpenMetricsLabelPairs( 320 w, metric.Label, additionalLabelName, additionalLabelValue, 321 ) 322 written += n 323 if err != nil { 324 return written, err 325 } 326 err = w.WriteByte(' ') 327 written++ 328 if err != nil { 329 return written, err 330 } 331 if useIntValue { 332 n, err = writeUint(w, intValue) 333 } else { 334 n, err = writeOpenMetricsFloat(w, floatValue) 335 } 336 written += n 337 if err != nil { 338 return written, err 339 } 340 if metric.TimestampMs != nil { 341 err = w.WriteByte(' ') 342 written++ 343 if err != nil { 344 return written, err 345 } 346 // TODO(beorn7): Format this directly without converting to a float first. 347 n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000) 348 written += n 349 if err != nil { 350 return written, err 351 } 352 } 353 if exemplar != nil { 354 n, err = writeExemplar(w, exemplar) 355 written += n 356 if err != nil { 357 return written, err 358 } 359 } 360 err = w.WriteByte('\n') 361 written++ 362 if err != nil { 363 return written, err 364 } 365 return written, nil 366 } 367 368 // writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float 369 // in OpenMetrics style. 370 func writeOpenMetricsLabelPairs( 371 w enhancedWriter, 372 in []*dto.LabelPair, 373 additionalLabelName string, additionalLabelValue float64, 374 ) (int, error) { 375 if len(in) == 0 && additionalLabelName == "" { 376 return 0, nil 377 } 378 var ( 379 written int 380 separator byte = '{' 381 ) 382 for _, lp := range in { 383 err := w.WriteByte(separator) 384 written++ 385 if err != nil { 386 return written, err 387 } 388 n, err := w.WriteString(lp.GetName()) 389 written += n 390 if err != nil { 391 return written, err 392 } 393 n, err = w.WriteString(`="`) 394 written += n 395 if err != nil { 396 return written, err 397 } 398 n, err = writeEscapedString(w, lp.GetValue(), true) 399 written += n 400 if err != nil { 401 return written, err 402 } 403 err = w.WriteByte('"') 404 written++ 405 if err != nil { 406 return written, err 407 } 408 separator = ',' 409 } 410 if additionalLabelName != "" { 411 err := w.WriteByte(separator) 412 written++ 413 if err != nil { 414 return written, err 415 } 416 n, err := w.WriteString(additionalLabelName) 417 written += n 418 if err != nil { 419 return written, err 420 } 421 n, err = w.WriteString(`="`) 422 written += n 423 if err != nil { 424 return written, err 425 } 426 n, err = writeOpenMetricsFloat(w, additionalLabelValue) 427 written += n 428 if err != nil { 429 return written, err 430 } 431 err = w.WriteByte('"') 432 written++ 433 if err != nil { 434 return written, err 435 } 436 } 437 err := w.WriteByte('}') 438 written++ 439 if err != nil { 440 return written, err 441 } 442 return written, nil 443 } 444 445 // writeExemplar writes the provided exemplar in OpenMetrics format to w. The 446 // function returns the number of bytes written and any error encountered. 447 func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) { 448 written := 0 449 n, err := w.WriteString(" # ") 450 written += n 451 if err != nil { 452 return written, err 453 } 454 n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0) 455 written += n 456 if err != nil { 457 return written, err 458 } 459 err = w.WriteByte(' ') 460 written++ 461 if err != nil { 462 return written, err 463 } 464 n, err = writeOpenMetricsFloat(w, e.GetValue()) 465 written += n 466 if err != nil { 467 return written, err 468 } 469 if e.Timestamp != nil { 470 err = w.WriteByte(' ') 471 written++ 472 if err != nil { 473 return written, err 474 } 475 err = (*e).Timestamp.CheckValid() 476 if err != nil { 477 return written, err 478 } 479 ts := (*e).Timestamp.AsTime() 480 // TODO(beorn7): Format this directly from components of ts to 481 // avoid overflow/underflow and precision issues of the float 482 // conversion. 483 n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9) 484 written += n 485 if err != nil { 486 return written, err 487 } 488 } 489 return written, nil 490 } 491 492 // writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting 493 // number would otherwise contain neither a "." nor an "e". 494 func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) { 495 switch { 496 case f == 1: 497 return w.WriteString("1.0") 498 case f == 0: 499 return w.WriteString("0.0") 500 case f == -1: 501 return w.WriteString("-1.0") 502 case math.IsNaN(f): 503 return w.WriteString("NaN") 504 case math.IsInf(f, +1): 505 return w.WriteString("+Inf") 506 case math.IsInf(f, -1): 507 return w.WriteString("-Inf") 508 default: 509 bp := numBufPool.Get().(*[]byte) 510 *bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64) 511 if !bytes.ContainsAny(*bp, "e.") { 512 *bp = append(*bp, '.', '0') 513 } 514 written, err := w.Write(*bp) 515 numBufPool.Put(bp) 516 return written, err 517 } 518 } 519 520 // writeUint is like writeInt just for uint64. 521 func writeUint(w enhancedWriter, u uint64) (int, error) { 522 bp := numBufPool.Get().(*[]byte) 523 *bp = strconv.AppendUint((*bp)[:0], u, 10) 524 written, err := w.Write(*bp) 525 numBufPool.Put(bp) 526 return written, err 527 }