github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/prometheus/prometheus.go (about) 1 // Copyright 2022 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package prometheus contains Prometheus-compliant metric data structures and utilities. 16 // It can export data in Prometheus data format, documented at: 17 // https://prometheus.io/docs/instrumenting/exposition_formats/ 18 package prometheus 19 20 import ( 21 "bufio" 22 "errors" 23 "fmt" 24 "io" 25 "math" 26 "reflect" 27 "sort" 28 "strings" 29 "time" 30 ) 31 32 // timeNow is the time.Now() function. Can be mocked in tests. 33 var timeNow = time.Now 34 35 // Prometheus label names used to identify each sandbox. 36 const ( 37 SandboxIDLabel = "sandbox" 38 PodNameLabel = "pod_name" 39 NamespaceLabel = "namespace_name" 40 IterationIDLabel = "iteration" 41 ) 42 43 // Type is a Prometheus metric type. 44 type Type int 45 46 // List of supported Prometheus metric types. 47 const ( 48 TypeUntyped = Type(iota) 49 TypeGauge 50 TypeCounter 51 TypeHistogram 52 ) 53 54 // Metric is a Prometheus metric metadata. 55 type Metric struct { 56 // Name is the Prometheus metric name. 57 Name string `json:"name"` 58 59 // Type is the type of the metric. 60 Type Type `json:"type"` 61 62 // Help is an optional helpful string explaining what the metric is about. 63 Help string `json:"help"` 64 } 65 66 // writeHeaderTo writes the metric comment header to the given writer. 67 func (m *Metric) writeHeaderTo(w io.Writer, options SnapshotExportOptions) error { 68 if m.Help != "" { 69 // This writes each string component one by one (rather than using fmt.Sprintf) 70 // in order to avoid allocating strings for each metric. 71 if _, err := io.WriteString(w, "# HELP "); err != nil { 72 return err 73 } 74 if _, err := io.WriteString(w, options.ExporterPrefix); err != nil { 75 return err 76 } 77 if _, err := io.WriteString(w, m.Name); err != nil { 78 return err 79 } 80 if _, err := io.WriteString(w, " "); err != nil { 81 return err 82 } 83 if _, err := writeEscapedString(w, m.Help, false); err != nil { 84 return err 85 } 86 if _, err := io.WriteString(w, "\n"); err != nil { 87 return err 88 } 89 } 90 var metricType string 91 switch m.Type { 92 case TypeGauge: 93 metricType = "gauge" 94 case TypeCounter: 95 metricType = "counter" 96 case TypeHistogram: 97 metricType = "histogram" 98 case TypeUntyped: 99 metricType = "untyped" 100 } 101 if metricType != "" { 102 if _, err := io.WriteString(w, "# TYPE "); err != nil { 103 return err 104 } 105 if _, err := io.WriteString(w, options.ExporterPrefix); err != nil { 106 return err 107 } 108 if _, err := io.WriteString(w, m.Name); err != nil { 109 return err 110 } 111 if _, err := io.WriteString(w, " "); err != nil { 112 return err 113 } 114 if _, err := io.WriteString(w, metricType); err != nil { 115 return err 116 } 117 if _, err := io.WriteString(w, "\n"); err != nil { 118 return err 119 } 120 } 121 return nil 122 } 123 124 // Number represents a numerical value. 125 // In Prometheus, all numbers are float64s. 126 // However, for the purpose of usage of this library, we support expressing numbers as integers, 127 // which makes things like counters much easier and more precise. 128 // At data export time (i.e. when written out in Prometheus data format), it is coallesced into 129 // a float. 130 type Number struct { 131 // Float is the float value of this number. 132 // Mutually exclusive with Int. 133 Float float64 `json:"float,omitempty"` 134 135 // Int is the integer value of this number. 136 // Mutually exclusive with Float. 137 Int int64 `json:"int,omitempty"` 138 } 139 140 // Common numbers which are reused and don't need their own memory allocations. 141 var ( 142 zero = Number{} 143 intOne = Number{Int: 1} 144 floatOne = Number{Float: 1.0} 145 floatNaN = Number{Float: math.NaN()} 146 floatInf = Number{Float: math.Inf(1)} 147 floatNegInf = Number{Float: math.Inf(-1)} 148 ) 149 150 // NewInt returns a new integer Number. 151 func NewInt(val int64) *Number { 152 switch val { 153 case 0: 154 return &zero 155 case 1: 156 return &intOne 157 default: 158 return &Number{Int: val} 159 } 160 } 161 162 // NewFloat returns a new floating-point Number. 163 func NewFloat(val float64) *Number { 164 if math.IsNaN(val) { 165 return &floatNaN 166 } 167 switch val { 168 case 0: 169 return &zero 170 case 1.0: 171 return &floatOne 172 case math.Inf(1.0): 173 return &floatInf 174 case math.Inf(-1.0): 175 return &floatNegInf 176 default: 177 return &Number{Float: val} 178 } 179 } 180 181 // IsInteger returns whether this number contains an integer value. 182 // This is defined as either having the `Float` part set to zero (in which case the `Int` part takes 183 // precedence), or having `Float` be a value equal to its own rounding and not a special float. 184 // 185 //go:nosplit 186 func (n *Number) IsInteger() bool { 187 if n.Float == 0 { 188 return true 189 } 190 if math.IsNaN(n.Float) || n.Float == math.Inf(-1) || n.Float == math.Inf(1) { 191 return false 192 } 193 return n.Float < float64(math.MaxInt64) && n.Float > float64(math.MinInt64) && math.Round(n.Float) == n.Float 194 } 195 196 // ToFloat returns this number as a floating-point number, regardless of which 197 // type the number was encoded as. An integer Number will have its value cast 198 // to a float, while a floating-point Number will have its value returned 199 // as-is. 200 // 201 //go:nosplit 202 func (n *Number) ToFloat() float64 { 203 if n.Int != 0 { 204 return float64(n.Int) 205 } 206 return n.Float 207 } 208 209 // String returns a string representation of this number. 210 func (n *Number) String() string { 211 var s strings.Builder 212 if err := n.writeTo(&s); err != nil { 213 panic(err) 214 } 215 return s.String() 216 } 217 218 // SameType returns true if `n` and `other` are either both floating-point or both integers. 219 // If a `Number` is zero, it is considered of the same type as any other zero `Number`. 220 // 221 //go:nosplit 222 func (n *Number) SameType(other *Number) bool { 223 // Within `n` and `other`, at least one of `Int` or `Float` must be set to zero. 224 // Therefore, this verifies that there is at least one shared zero between the two. 225 return n.Float == other.Float || n.Int == other.Int 226 } 227 228 // GreaterThan returns true if n > other. 229 // Precondition: n.SameType(other) is true. Panics otherwise. 230 // 231 //go:nosplit 232 func (n *Number) GreaterThan(other *Number) bool { 233 if !n.SameType(other) { 234 panic("tried to compare two numbers of different types") 235 } 236 if n.IsInteger() { 237 return n.Int > other.Int 238 } 239 return n.Float > other.Float 240 } 241 242 // WriteInteger writes the given integer to a writer without allocating strings. 243 // 244 //go:nosplit 245 func WriteInteger(w io.Writer, val int64) (int, error) { 246 const decimalDigits = "0123456789" 247 if val == 0 { 248 return io.WriteString(w, decimalDigits[0:1]) 249 } 250 var written int 251 if val < 0 { 252 n, err := io.WriteString(w, "-") 253 written += n 254 if err != nil { 255 return written, err 256 } 257 val = -val 258 } 259 decimal := int64(1) 260 for ; val/decimal != 0; decimal *= 10 { 261 } 262 for decimal /= 10; decimal > 0; decimal /= 10 { 263 digit := (val / decimal) % 10 264 n, err := io.WriteString(w, decimalDigits[digit:digit+1]) 265 written += n 266 if err != nil { 267 return written, err 268 } 269 } 270 return written, nil 271 } 272 273 // writeTo writes the number to the given writer. 274 // This only causes heap allocations when the number is a non-zero, non-special float. 275 func (n *Number) writeTo(w io.Writer) error { 276 var s string 277 switch { 278 // Zero case: 279 case n.Int == 0 && n.Float == 0: 280 s = "0" 281 282 // Integer case: 283 case n.Int != 0: 284 _, err := WriteInteger(w, n.Int) 285 return err 286 287 // Special float cases: 288 case n.Float == math.Inf(-1): 289 s = "-Inf" 290 case n.Float == math.Inf(1): 291 s = "+Inf" 292 case math.IsNaN(n.Float): 293 s = "NaN" 294 295 // Regular float case: 296 default: 297 s = fmt.Sprintf("%f", n.Float) 298 } 299 _, err := io.WriteString(w, s) 300 return err 301 } 302 303 // Bucket is a single histogram bucket. 304 type Bucket struct { 305 // UpperBound is the upper bound of the bucket. 306 // The lower bound of the bucket is the largest UpperBound within other Histogram Buckets that 307 // is smaller than this bucket's UpperBound. 308 // The bucket with the smallest UpperBound within a Histogram implicitly has -Inf as lower bound. 309 // This should be set to +Inf to mark the "last" bucket. 310 UpperBound Number `json:"le"` 311 312 // Samples is the number of samples in the bucket. 313 // Note: When exported to Prometheus, they are exported cumulatively, i.e. the count of samples 314 // exported in Bucket i is actually sum(histogram.Buckets[j].Samples for 0 <= j <= i). 315 Samples uint64 `json:"n,omitempty"` 316 } 317 318 // Histogram contains data about histogram values. 319 type Histogram struct { 320 // Total is the sum of sample values across all buckets. 321 Total Number `json:"total"` 322 // Min is the minimum sample ever recorded in this histogram. 323 Min Number `json:"min"` 324 // Max is the maximum sample ever recorded in this histogram. 325 Max Number `json:"max"` 326 // SumOfSquaredDeviations is the number of squared deviations of all samples. 327 SumOfSquaredDeviations Number `json:"ssd"` 328 // Buckets contains per-bucket data. 329 // A distribution with n finite-boundary buckets should have n+2 entries here. 330 // The 0th entry is the underflow bucket (i.e. the one with -inf as lower bound), 331 // and the last aka (n+1)th entry is the overflow bucket (i.e. the one with +inf as upper bound). 332 Buckets []Bucket `json:"buckets,omitempty"` 333 } 334 335 // Data is an observation of the value of a single metric at a certain point in time. 336 type Data struct { 337 // Metric is the metric for which the value is being reported. 338 Metric *Metric `json:"metric"` 339 340 // Labels is a key-value pair representing the labels set on this metric. 341 // This may be merged with other labels during export. 342 Labels map[string]string `json:"labels,omitempty"` 343 344 // ExternalLabels are more labels merged together with `Labels`. 345 // They can be set using SetExternalLabels. 346 // They are useful in the case where a single Data needs labels from two sources: 347 // labels specific to this data point (which should be in `Labels`), and labels 348 // that are shared between multiple data points (stored in `ExternalLabels`). 349 // This avoids allocating unique `Labels` maps for each Data struct, when 350 // most of the actual labels would be shared between them. 351 ExternalLabels map[string]string `json:"external_labels,omitempty"` 352 353 // At most one of the fields below may be set. 354 // Which one depends on the type of the metric. 355 356 // Number is used for all numerical types. 357 Number *Number `json:"val,omitempty"` 358 359 // Histogram is used for histogram-typed metrics. 360 HistogramValue *Histogram `json:"histogram,omitempty"` 361 } 362 363 // NewIntData returns a new Data struct with the given metric and value. 364 func NewIntData(metric *Metric, val int64) *Data { 365 return LabeledIntData(metric, nil, val) 366 } 367 368 // LabeledIntData returns a new Data struct with the given metric, labels, and value. 369 func LabeledIntData(metric *Metric, labels map[string]string, val int64) *Data { 370 return &Data{Metric: metric, Labels: labels, Number: NewInt(val)} 371 } 372 373 // NewFloatData returns a new Data struct with the given metric and value. 374 func NewFloatData(metric *Metric, val float64) *Data { 375 return LabeledFloatData(metric, nil, val) 376 } 377 378 // LabeledFloatData returns a new Data struct with the given metric, labels, and value. 379 func LabeledFloatData(metric *Metric, labels map[string]string, val float64) *Data { 380 return &Data{Metric: metric, Labels: labels, Number: NewFloat(val)} 381 } 382 383 // SetExternalLabels sets d.ExternalLabels. See its docstring for more information. 384 // Returns `d` for chainability. 385 func (d *Data) SetExternalLabels(externalLabels map[string]string) *Data { 386 d.ExternalLabels = externalLabels 387 return d 388 } 389 390 // ExportOptions contains options that control how metric data is exported in Prometheus format. 391 type ExportOptions struct { 392 // CommentHeader is prepended as a comment before any metric data is exported. 393 CommentHeader string 394 395 // MetricsWritten memoizes written metric preambles (help/type comments) 396 // by metric name. 397 // If specified, this map can be used to avoid duplicate preambles across multiple snapshots. 398 // Note that this map is modified in-place during the writing process. 399 MetricsWritten map[string]bool 400 } 401 402 // SnapshotExportOptions contains options that control how metric data is exported for an 403 // individual Snapshot. 404 type SnapshotExportOptions struct { 405 // ExporterPrefix is prepended to all metric names. 406 ExporterPrefix string 407 408 // ExtraLabels is added as labels for all metric values. 409 ExtraLabels map[string]string 410 } 411 412 // writeEscapedString writes the given string in quotation marks and with some characters escaped, 413 // per Prometheus spec. It does this without string allocations. 414 // If `quoted` is true, quote characters will surround the string, and quote characters within `s` 415 // will also be escaped. 416 func writeEscapedString(w io.Writer, s string, quoted bool) (int, error) { 417 const ( 418 quote = '"' 419 backslash = '\\' 420 newline = '\n' 421 quoteStr = `"` 422 escapedQuote = `\\"` 423 escapedBackslash = "\\\\" 424 escapedNewline = "\\\n" 425 ) 426 written := 0 427 var n int 428 var err error 429 if quoted { 430 n, err = io.WriteString(w, quoteStr) 431 written += n 432 if err != nil { 433 return written, err 434 } 435 } 436 for _, r := range s { 437 switch r { 438 case quote: 439 if quoted { 440 n, err = io.WriteString(w, escapedQuote) 441 } else { 442 n, err = io.WriteString(w, quoteStr) 443 } 444 case backslash: 445 n, err = io.WriteString(w, escapedBackslash) 446 case newline: 447 n, err = io.WriteString(w, escapedNewline) 448 default: 449 n, err = io.WriteString(w, string(r)) 450 } 451 written += n 452 if err != nil { 453 return written, err 454 } 455 } 456 if quoted { 457 n, err = io.WriteString(w, quoteStr) 458 written += n 459 if err != nil { 460 return written, err 461 } 462 } 463 return written, nil 464 } 465 466 // writeMetricPreambleTo writes the metric name to the io.Writer. It may also 467 // write unwritten help and type comments of the metric if they haven't been 468 // written to the io.Writer yet. 469 func (d *Data) writeMetricPreambleTo(w io.Writer, options SnapshotExportOptions, metricsWritten map[string]bool) error { 470 // Metric header, if we haven't printed it yet. 471 if !metricsWritten[d.Metric.Name] { 472 // Extra newline before each preamble for aesthetic reasons. 473 if _, err := io.WriteString(w, "\n"); err != nil { 474 return err 475 } 476 if err := d.Metric.writeHeaderTo(w, options); err != nil { 477 return err 478 } 479 metricsWritten[d.Metric.Name] = true 480 } 481 482 // Metric name. 483 if options.ExporterPrefix != "" { 484 if _, err := io.WriteString(w, options.ExporterPrefix); err != nil { 485 return err 486 } 487 } 488 if _, err := io.WriteString(w, d.Metric.Name); err != nil { 489 return err 490 } 491 return nil 492 } 493 494 // keyVal is a key-value pair used in the function below. 495 type keyVal struct{ Key, Value string } 496 497 // sortedIterateLabels iterates through labels and outputs them to `out` in sorted key order, 498 // or stops when cancelCh is written to. It runs in O(n^2) time but makes no heap allocations. 499 func sortedIterateLabels(labels map[string]string, out chan<- keyVal, cancelCh <-chan struct{}) { 500 defer close(out) 501 if len(labels) == 0 { 502 return 503 } 504 505 // smallestKey is the smallest key that we've already sent to `out`. 506 // It starts as the empty string, which means we haven't sent anything to `out` yet. 507 smallestKey := "" 508 // Find the smallest key of the whole set and send it out. 509 for k := range labels { 510 if smallestKey == "" || k < smallestKey { 511 smallestKey = k 512 } 513 } 514 select { 515 case out <- keyVal{smallestKey, labels[smallestKey]}: 516 case <-cancelCh: 517 return 518 } 519 520 // Iterate until we've sent as many items as we have as input to the output channel. 521 // We start at 1 because the loop above already sent out the smallest key to `out`. 522 for numOutput := 1; numOutput < len(labels); numOutput++ { 523 // nextSmallestKey is the smallest key that is strictly larger than `smallestKey`. 524 nextSmallestKey := "" 525 for k := range labels { 526 if k > smallestKey && (nextSmallestKey == "" || k < nextSmallestKey) { 527 nextSmallestKey = k 528 } 529 } 530 531 // Update smallestKey and send it out. 532 smallestKey = nextSmallestKey 533 select { 534 case out <- keyVal{smallestKey, labels[smallestKey]}: 535 case <-cancelCh: 536 return 537 } 538 } 539 } 540 541 // LabelOrError is used in OrderedLabels. 542 // It represents either a key-value pair, or an error. 543 type LabelOrError struct { 544 Key, Value string 545 Error error 546 } 547 548 // OrderedLabels streams the list of 'label_key="label_value"' in sorted order, except "le" which is 549 // a reserved Prometheus label name and should go last. 550 // If an error is encountered, it is returned as the Error field of LabelOrError, and no further 551 // messages will be sent on the channel. 552 func OrderedLabels(labels ...map[string]string) <-chan LabelOrError { 553 // This function is quite hot on the metric-rendering path, and its naive "just put all the 554 // strings in one map to ensure no dupes it, then in one slice and sort it" approach is very 555 // allocation-heavy. This approach is more computation-heavy (it runs in 556 // O(len(labels) * len(largest label map))), but the only heap allocations it does is for the 557 // following tiny slices and channels. In practice, the number of label maps and the size of 558 // each label map is tiny, so this is worth doing despite the theoretically-longer run time. 559 560 // Initialize the channels we'll use. 561 mapChannels := make([]chan keyVal, 0, len(labels)) 562 lastKeyVal := make([]keyVal, len(labels)) 563 resultCh := make(chan LabelOrError) 564 var cancelCh chan struct{} 565 // outputError is a helper function for when we have encountered an error mid-way. 566 outputError := func(err error) { 567 if cancelCh != nil { 568 for range mapChannels { 569 cancelCh <- struct{}{} 570 } 571 close(cancelCh) 572 } 573 resultCh <- LabelOrError{Error: err} 574 close(resultCh) 575 } 576 577 // Verify that no label is the empty string. It's not a valid label name, 578 // and we use the empty string later on in the function as a marker of having 579 // finished processing all labels from a given label map. 580 for _, labelMap := range labels { 581 for label := range labelMap { 582 if label == "" { 583 go outputError(errors.New("got empty-string label")) 584 return resultCh 585 } 586 } 587 } 588 589 // Each label map is processed in its own goroutine, 590 // which will stream it back to this function in sorted order. 591 cancelCh = make(chan struct{}, len(labels)) 592 for _, labelMap := range labels { 593 ch := make(chan keyVal) 594 mapChannels = append(mapChannels, ch) 595 go sortedIterateLabels(labelMap, ch, cancelCh) 596 } 597 598 // This goroutine is the meat of this function; it iterates through 599 // the results being streamed from each `sortedIterateLabels` goroutine 600 // that we spawned earlier, until all of them are exhausted or until we 601 // hit an error. 602 go func() { 603 // The "le" label is special and goes last, not in sorted order. 604 // gotLe is the empty string if there is no "le" label, 605 // otherwise it's the value of the "le" label. 606 var gotLe string 607 608 // numChannelsLeft tracks the number of channels that are still live. 609 for numChannelsLeft := len(mapChannels); numChannelsLeft > 0; { 610 // Iterate over all channels and ensure we have the freshest (smallest) 611 // label from each of them. 612 for i, ch := range mapChannels { 613 // A nil channel is one that has been closed. 614 if ch == nil { 615 continue 616 } 617 // If we already have the latest value from this channel, 618 // keep it there instead of getting a new one, 619 if lastKeyVal[i].Key != "" { 620 continue 621 } 622 // Otherwise, get a new label. 623 kv, open := <-ch 624 if !open { 625 // Channel has been closed, no more to read from this one. 626 numChannelsLeft-- 627 mapChannels[i] = nil 628 continue 629 } 630 if kv.Key == "le" { 631 if gotLe != "" { 632 outputError(errors.New("got duplicate 'le' label")) 633 return 634 } 635 gotLe = kv.Value 636 continue 637 } 638 lastKeyVal[i] = kv 639 } 640 641 // We have one key-value pair from each still-active channel now. 642 // Find the smallest one between them. 643 smallestKey := "" 644 indexForSmallest := -1 645 for i, kv := range lastKeyVal { 646 if kv.Key == "" { 647 continue 648 } 649 if smallestKey == "" || kv.Key < smallestKey { 650 smallestKey = kv.Key 651 indexForSmallest = i 652 } else if kv.Key == smallestKey { 653 outputError(fmt.Errorf("got duplicate label %q", smallestKey)) 654 return 655 } 656 } 657 658 if indexForSmallest == -1 { 659 // There are no more key-value pairs to output. We're done. 660 break 661 } 662 663 // Output the smallest key-value pairs out of all the channels. 664 resultCh <- LabelOrError{ 665 Key: smallestKey, 666 Value: lastKeyVal[indexForSmallest].Value, 667 } 668 // Mark the last key-value pair from the channel that gave us the 669 // smallest key-value pair as no longer present, so that we get a new 670 // key-value pair from it in the next iteration. 671 lastKeyVal[indexForSmallest] = keyVal{} 672 } 673 674 // Output the "le" label last. 675 if gotLe != "" { 676 resultCh <- LabelOrError{ 677 Key: "le", 678 Value: gotLe, 679 } 680 } 681 close(resultCh) 682 close(cancelCh) 683 }() 684 685 return resultCh 686 } 687 688 // writeLabelsTo writes a set of metric labels. 689 func (d *Data) writeLabelsTo(w io.Writer, extraLabels map[string]string, leLabel *Number) error { 690 if len(d.Labels)+len(d.ExternalLabels)+len(extraLabels) != 0 || leLabel != nil { 691 if _, err := io.WriteString(w, "{"); err != nil { 692 return err 693 } 694 var orderedLabels <-chan LabelOrError 695 if leLabel != nil { 696 orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels, map[string]string{"le": leLabel.String()}) 697 } else { 698 orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels) 699 } 700 firstLabel := true 701 var foundError error 702 for labelOrError := range orderedLabels { 703 if foundError != nil { 704 continue 705 } 706 if labelOrError.Error != nil { 707 foundError = labelOrError.Error 708 continue 709 } 710 if !firstLabel { 711 if _, err := io.WriteString(w, ","); err != nil { 712 return err 713 } 714 } 715 firstLabel = false 716 if _, err := io.WriteString(w, labelOrError.Key); err != nil { 717 return err 718 } 719 if _, err := io.WriteString(w, "="); err != nil { 720 return err 721 } 722 if _, err := writeEscapedString(w, labelOrError.Value, true); err != nil { 723 return err 724 } 725 } 726 if foundError != nil { 727 return foundError 728 } 729 if _, err := io.WriteString(w, "}"); err != nil { 730 return err 731 } 732 } 733 return nil 734 } 735 736 // writeMetricLine writes a single line with a single number (val) to w. 737 func (d *Data) writeMetricLine(w io.Writer, metricSuffix string, val *Number, when time.Time, options SnapshotExportOptions, leLabel *Number, metricsWritten map[string]bool) error { 738 if err := d.writeMetricPreambleTo(w, options, metricsWritten); err != nil { 739 return err 740 } 741 if metricSuffix != "" { 742 if _, err := io.WriteString(w, metricSuffix); err != nil { 743 return err 744 } 745 } 746 if err := d.writeLabelsTo(w, options.ExtraLabels, leLabel); err != nil { 747 return err 748 } 749 if _, err := io.WriteString(w, " "); err != nil { 750 return err 751 } 752 if err := val.writeTo(w); err != nil { 753 return err 754 } 755 if _, err := io.WriteString(w, " "); err != nil { 756 return err 757 } 758 if _, err := WriteInteger(w, when.UnixMilli()); err != nil { 759 return err 760 } 761 if _, err := io.WriteString(w, "\n"); err != nil { 762 return err 763 } 764 return nil 765 } 766 767 // writeTo writes the Data to the given writer in Prometheus format. 768 func (d *Data) writeTo(w io.Writer, when time.Time, options SnapshotExportOptions, metricsWritten map[string]bool) error { 769 switch d.Metric.Type { 770 case TypeUntyped, TypeGauge, TypeCounter: 771 return d.writeMetricLine(w, "", d.Number, when, options, nil, metricsWritten) 772 case TypeHistogram: 773 // Write an empty line before and after histograms to easily distinguish them from 774 // other metric lines. 775 if _, err := io.WriteString(w, "\n"); err != nil { 776 return err 777 } 778 var numSamples uint64 779 var samples Number 780 for _, bucket := range d.HistogramValue.Buckets { 781 numSamples += bucket.Samples 782 samples.Int = int64(numSamples) // Prometheus distribution bucket counts are cumulative. 783 if err := d.writeMetricLine(w, "_bucket", &samples, when, options, &bucket.UpperBound, metricsWritten); err != nil { 784 return err 785 } 786 } 787 if err := d.writeMetricLine(w, "_sum", &d.HistogramValue.Total, when, options, nil, metricsWritten); err != nil { 788 return err 789 } 790 samples.Int = int64(numSamples) 791 if err := d.writeMetricLine(w, "_count", &samples, when, options, nil, metricsWritten); err != nil { 792 return err 793 } 794 if err := d.writeMetricLine(w, "_min", &d.HistogramValue.Min, when, options, nil, metricsWritten); err != nil { 795 return err 796 } 797 if err := d.writeMetricLine(w, "_max", &d.HistogramValue.Max, when, options, nil, metricsWritten); err != nil { 798 return err 799 } 800 if err := d.writeMetricLine(w, "_ssd", &d.HistogramValue.SumOfSquaredDeviations, when, options, nil, metricsWritten); err != nil { 801 return err 802 } 803 // Empty line after the histogram. 804 if _, err := io.WriteString(w, "\n"); err != nil { 805 return err 806 } 807 return nil 808 default: 809 return fmt.Errorf("unknown metric type for metric %s: %v", d.Metric.Name, d.Metric.Type) 810 } 811 } 812 813 // Snapshot is a snapshot of the values of all the metrics at a certain point in time. 814 type Snapshot struct { 815 // When is the timestamp at which the snapshot was taken. 816 // Note that Prometheus ultimately encodes timestamps as millisecond-precision int64s from epoch. 817 When time.Time `json:"when,omitempty"` 818 819 // Data is the whole snapshot data. 820 // Each Data must be a unique combination of (Metric, Labels) within a Snapshot. 821 Data []*Data `json:"data,omitempty"` 822 } 823 824 // NewSnapshot returns a new Snapshot at the current time. 825 func NewSnapshot() *Snapshot { 826 return &Snapshot{When: timeNow()} 827 } 828 829 // Add data point(s) to the snapshot. 830 // Returns itself for chainability. 831 func (s *Snapshot) Add(data ...*Data) *Snapshot { 832 s.Data = append(s.Data, data...) 833 return s 834 } 835 836 // countingWriter implements io.Writer, and counts the number of bytes written to it. 837 // Useful in this file to keep track of total number of bytes without having to plumb this 838 // everywhere in the writeX() functions in this file. 839 type countingWriter struct { 840 w *bufio.Writer 841 written int 842 } 843 844 // Write implements io.Writer.Write. 845 func (w *countingWriter) Write(b []byte) (int, error) { 846 written, err := w.w.Write(b) 847 w.written += written 848 return written, err 849 } 850 851 // WriteString implements io.StringWriter.WriteString. 852 // This avoids going into the slow, allocation-heavy path of io.WriteString. 853 func (w *countingWriter) WriteString(s string) (int, error) { 854 written, err := w.w.WriteString(s) 855 w.written += written 856 return written, err 857 } 858 859 // Written returns the number of bytes written to the underlying writer (minus buffered writes). 860 func (w *countingWriter) Written() int { 861 return w.written - w.w.Buffered() 862 } 863 864 // writeSingleMetric writes the data to the given writer in Prometheus format. 865 // It returns the number of bytes written. 866 func (s *Snapshot) writeSingleMetric(w io.Writer, options SnapshotExportOptions, metricName string, metricsWritten map[string]bool) error { 867 if !strings.HasPrefix(metricName, options.ExporterPrefix) { 868 return nil 869 } 870 wantMetricName := strings.TrimPrefix(metricName, options.ExporterPrefix) 871 for _, d := range s.Data { 872 if d.Metric.Name != wantMetricName { 873 continue 874 } 875 if err := d.writeTo(w, s.When, options, metricsWritten); err != nil { 876 return err 877 } 878 } 879 return nil 880 } 881 882 // Write writes one or more snapshots to the writer. 883 // This ensures same-name metrics across different snapshots are printed together, per spec. 884 func Write(w io.Writer, options ExportOptions, snapshotsToOptions map[*Snapshot]SnapshotExportOptions) (int, error) { 885 if len(snapshotsToOptions) == 0 { 886 return 0, nil 887 } 888 cw := &countingWriter{w: bufio.NewWriter(w)} 889 if options.CommentHeader != "" { 890 for _, commentLine := range strings.Split(options.CommentHeader, "\n") { 891 if _, err := io.WriteString(cw, "# "); err != nil { 892 return cw.Written(), err 893 } 894 if _, err := io.WriteString(cw, commentLine); err != nil { 895 return cw.Written(), err 896 } 897 if _, err := io.WriteString(cw, "\n"); err != nil { 898 return cw.Written(), err 899 } 900 } 901 } 902 snapshots := make([]*Snapshot, 0, len(snapshotsToOptions)) 903 for snapshot := range snapshotsToOptions { 904 snapshots = append(snapshots, snapshot) 905 } 906 switch len(snapshots) { 907 case 1: // Single-snapshot case. 908 if _, err := io.WriteString(cw, fmt.Sprintf("# Writing data from snapshot containing %d data points taken at %v.\n", len(snapshots[0].Data), snapshots[0].When)); err != nil { 909 return cw.Written(), err 910 } 911 default: // Multi-snapshot case. 912 // Provide a consistent ordering of snapshots. 913 sort.Slice(snapshots, func(i, j int) bool { 914 return reflect.ValueOf(snapshots[i]).Pointer() < reflect.ValueOf(snapshots[j]).Pointer() 915 }) 916 if _, err := io.WriteString(cw, fmt.Sprintf("# Writing data from %d snapshots:\n", len(snapshots))); err != nil { 917 return cw.Written(), err 918 } 919 for _, snapshot := range snapshots { 920 if _, err := io.WriteString(cw, fmt.Sprintf("# - Snapshot with %d data points taken at %v: %v\n", len(snapshot.Data), snapshot.When, snapshotsToOptions[snapshot].ExtraLabels)); err != nil { 921 return cw.Written(), err 922 } 923 } 924 } 925 if _, err := io.WriteString(cw, "\n"); err != nil { 926 return cw.Written(), err 927 } 928 if options.MetricsWritten == nil { 929 options.MetricsWritten = make(map[string]bool) 930 } 931 metricNamesMap := make(map[string]bool, len(options.MetricsWritten)) 932 metricNames := make([]string, 0, len(options.MetricsWritten)) 933 for _, snapshot := range snapshots { 934 for _, data := range snapshot.Data { 935 metricName := snapshotsToOptions[snapshot].ExporterPrefix + data.Metric.Name 936 if !metricNamesMap[metricName] { 937 metricNamesMap[metricName] = true 938 metricNames = append(metricNames, metricName) 939 } 940 } 941 } 942 sort.Strings(metricNames) 943 for _, metricName := range metricNames { 944 for _, snapshot := range snapshots { 945 snapshot.writeSingleMetric(cw, snapshotsToOptions[snapshot], metricName, options.MetricsWritten) 946 } 947 } 948 if _, err := io.WriteString(cw, "\n# End of metric data.\n"); err != nil { 949 return cw.Written(), err 950 } 951 if err := cw.w.Flush(); err != nil { 952 return cw.Written(), err 953 } 954 return cw.Written(), nil 955 }