github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/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 func writeInteger(w io.Writer, val int64) (int, error) { 244 const decimalDigits = "0123456789" 245 if val == 0 { 246 return io.WriteString(w, decimalDigits[0:1]) 247 } 248 var written int 249 if val < 0 { 250 n, err := io.WriteString(w, "-") 251 written += n 252 if err != nil { 253 return written, err 254 } 255 val = -val 256 } 257 decimal := int64(1) 258 for ; val/decimal != 0; decimal *= 10 { 259 } 260 for decimal /= 10; decimal > 0; decimal /= 10 { 261 digit := (val / decimal) % 10 262 n, err := io.WriteString(w, decimalDigits[digit:digit+1]) 263 written += n 264 if err != nil { 265 return written, err 266 } 267 } 268 return written, nil 269 } 270 271 // writeTo writes the number to the given writer. 272 // This only causes heap allocations when the number is a non-zero, non-special float. 273 func (n *Number) writeTo(w io.Writer) error { 274 var s string 275 switch { 276 // Zero case: 277 case n.Int == 0 && n.Float == 0: 278 s = "0" 279 280 // Integer case: 281 case n.Int != 0: 282 _, err := writeInteger(w, n.Int) 283 return err 284 285 // Special float cases: 286 case n.Float == math.Inf(-1): 287 s = "-Inf" 288 case n.Float == math.Inf(1): 289 s = "+Inf" 290 case math.IsNaN(n.Float): 291 s = "NaN" 292 293 // Regular float case: 294 default: 295 s = fmt.Sprintf("%f", n.Float) 296 } 297 _, err := io.WriteString(w, s) 298 return err 299 } 300 301 // Bucket is a single histogram bucket. 302 type Bucket struct { 303 // UpperBound is the upper bound of the bucket. 304 // The lower bound of the bucket is the largest UpperBound within other Histogram Buckets that 305 // is smaller than this bucket's UpperBound. 306 // The bucket with the smallest UpperBound within a Histogram implicitly has -Inf as lower bound. 307 // This should be set to +Inf to mark the "last" bucket. 308 UpperBound Number `json:"le"` 309 310 // Samples is the number of samples in the bucket. 311 // Note: When exported to Prometheus, they are exported cumulatively, i.e. the count of samples 312 // exported in Bucket i is actually sum(histogram.Buckets[j].Samples for 0 <= j <= i). 313 Samples uint64 `json:"n,omitempty"` 314 } 315 316 // Histogram contains data about histogram values. 317 type Histogram struct { 318 // Total is the sum of sample values across all buckets. 319 Total Number `json:"total"` 320 // Min is the minimum sample ever recorded in this histogram. 321 Min Number `json:"min"` 322 // Max is the maximum sample ever recorded in this histogram. 323 Max Number `json:"max"` 324 // SumOfSquaredDeviations is the number of squared deviations of all samples. 325 SumOfSquaredDeviations Number `json:"ssd"` 326 // Buckets contains per-bucket data. 327 // A distribution with n finite-boundary buckets should have n+2 entries here. 328 // The 0th entry is the underflow bucket (i.e. the one with -inf as lower bound), 329 // and the last aka (n+1)th entry is the overflow bucket (i.e. the one with +inf as upper bound). 330 Buckets []Bucket `json:"buckets,omitempty"` 331 } 332 333 // Data is an observation of the value of a single metric at a certain point in time. 334 type Data struct { 335 // Metric is the metric for which the value is being reported. 336 Metric *Metric `json:"metric"` 337 338 // Labels is a key-value pair representing the labels set on this metric. 339 // This may be merged with other labels during export. 340 Labels map[string]string `json:"labels,omitempty"` 341 342 // ExternalLabels are more labels merged together with `Labels`. 343 // They can be set using SetExternalLabels. 344 // They are useful in the case where a single Data needs labels from two sources: 345 // labels specific to this data point (which should be in `Labels`), and labels 346 // that are shared between multiple data points (stored in `ExternalLabels`). 347 // This avoids allocating unique `Labels` maps for each Data struct, when 348 // most of the actual labels would be shared between them. 349 ExternalLabels map[string]string `json:"external_labels,omitempty"` 350 351 // At most one of the fields below may be set. 352 // Which one depends on the type of the metric. 353 354 // Number is used for all numerical types. 355 Number *Number `json:"val,omitempty"` 356 357 // Histogram is used for histogram-typed metrics. 358 HistogramValue *Histogram `json:"histogram,omitempty"` 359 } 360 361 // NewIntData returns a new Data struct with the given metric and value. 362 func NewIntData(metric *Metric, val int64) *Data { 363 return LabeledIntData(metric, nil, val) 364 } 365 366 // LabeledIntData returns a new Data struct with the given metric, labels, and value. 367 func LabeledIntData(metric *Metric, labels map[string]string, val int64) *Data { 368 return &Data{Metric: metric, Labels: labels, Number: NewInt(val)} 369 } 370 371 // NewFloatData returns a new Data struct with the given metric and value. 372 func NewFloatData(metric *Metric, val float64) *Data { 373 return LabeledFloatData(metric, nil, val) 374 } 375 376 // LabeledFloatData returns a new Data struct with the given metric, labels, and value. 377 func LabeledFloatData(metric *Metric, labels map[string]string, val float64) *Data { 378 return &Data{Metric: metric, Labels: labels, Number: NewFloat(val)} 379 } 380 381 // SetExternalLabels sets d.ExternalLabels. See its docstring for more information. 382 // Returns `d` for chainability. 383 func (d *Data) SetExternalLabels(externalLabels map[string]string) *Data { 384 d.ExternalLabels = externalLabels 385 return d 386 } 387 388 // ExportOptions contains options that control how metric data is exported in Prometheus format. 389 type ExportOptions struct { 390 // CommentHeader is prepended as a comment before any metric data is exported. 391 CommentHeader string 392 393 // MetricsWritten memoizes written metric preambles (help/type comments) 394 // by metric name. 395 // If specified, this map can be used to avoid duplicate preambles across multiple snapshots. 396 // Note that this map is modified in-place during the writing process. 397 MetricsWritten map[string]bool 398 } 399 400 // SnapshotExportOptions contains options that control how metric data is exported for an 401 // individual Snapshot. 402 type SnapshotExportOptions struct { 403 // ExporterPrefix is prepended to all metric names. 404 ExporterPrefix string 405 406 // ExtraLabels is added as labels for all metric values. 407 ExtraLabels map[string]string 408 } 409 410 // writeEscapedString writes the given string in quotation marks and with some characters escaped, 411 // per Prometheus spec. It does this without string allocations. 412 // If `quoted` is true, quote characters will surround the string, and quote characters within `s` 413 // will also be escaped. 414 func writeEscapedString(w io.Writer, s string, quoted bool) (int, error) { 415 const ( 416 quote = '"' 417 backslash = '\\' 418 newline = '\n' 419 quoteStr = `"` 420 escapedQuote = `\\"` 421 escapedBackslash = "\\\\" 422 escapedNewline = "\\\n" 423 ) 424 written := 0 425 var n int 426 var err error 427 if quoted { 428 n, err = io.WriteString(w, quoteStr) 429 written += n 430 if err != nil { 431 return written, err 432 } 433 } 434 for _, r := range s { 435 switch r { 436 case quote: 437 if quoted { 438 n, err = io.WriteString(w, escapedQuote) 439 } else { 440 n, err = io.WriteString(w, quoteStr) 441 } 442 case backslash: 443 n, err = io.WriteString(w, escapedBackslash) 444 case newline: 445 n, err = io.WriteString(w, escapedNewline) 446 default: 447 n, err = io.WriteString(w, string(r)) 448 } 449 written += n 450 if err != nil { 451 return written, err 452 } 453 } 454 if quoted { 455 n, err = io.WriteString(w, quoteStr) 456 written += n 457 if err != nil { 458 return written, err 459 } 460 } 461 return written, nil 462 } 463 464 // writeMetricPreambleTo writes the metric name to the io.Writer. It may also 465 // write unwritten help and type comments of the metric if they haven't been 466 // written to the io.Writer yet. 467 func (d *Data) writeMetricPreambleTo(w io.Writer, options SnapshotExportOptions, metricsWritten map[string]bool) error { 468 // Metric header, if we haven't printed it yet. 469 if !metricsWritten[d.Metric.Name] { 470 // Extra newline before each preamble for aesthetic reasons. 471 if _, err := io.WriteString(w, "\n"); err != nil { 472 return err 473 } 474 if err := d.Metric.writeHeaderTo(w, options); err != nil { 475 return err 476 } 477 metricsWritten[d.Metric.Name] = true 478 } 479 480 // Metric name. 481 if options.ExporterPrefix != "" { 482 if _, err := io.WriteString(w, options.ExporterPrefix); err != nil { 483 return err 484 } 485 } 486 if _, err := io.WriteString(w, d.Metric.Name); err != nil { 487 return err 488 } 489 return nil 490 } 491 492 // keyVal is a key-value pair used in the function below. 493 type keyVal struct{ Key, Value string } 494 495 // sortedIterateLabels iterates through labels and outputs them to `out` in sorted key order, 496 // or stops when cancelCh is written to. It runs in O(n^2) time but makes no heap allocations. 497 func sortedIterateLabels(labels map[string]string, out chan<- keyVal, cancelCh <-chan struct{}) { 498 defer close(out) 499 if len(labels) == 0 { 500 return 501 } 502 503 // smallestKey is the smallest key that we've already sent to `out`. 504 // It starts as the empty string, which means we haven't sent anything to `out` yet. 505 smallestKey := "" 506 // Find the smallest key of the whole set and send it out. 507 for k := range labels { 508 if smallestKey == "" || k < smallestKey { 509 smallestKey = k 510 } 511 } 512 select { 513 case out <- keyVal{smallestKey, labels[smallestKey]}: 514 case <-cancelCh: 515 return 516 } 517 518 // Iterate until we've sent as many items as we have as input to the output channel. 519 // We start at 1 because the loop above already sent out the smallest key to `out`. 520 for numOutput := 1; numOutput < len(labels); numOutput++ { 521 // nextSmallestKey is the smallest key that is strictly larger than `smallestKey`. 522 nextSmallestKey := "" 523 for k := range labels { 524 if k > smallestKey && (nextSmallestKey == "" || k < nextSmallestKey) { 525 nextSmallestKey = k 526 } 527 } 528 529 // Update smallestKey and send it out. 530 smallestKey = nextSmallestKey 531 select { 532 case out <- keyVal{smallestKey, labels[smallestKey]}: 533 case <-cancelCh: 534 return 535 } 536 } 537 } 538 539 // LabelOrError is used in OrderedLabels. 540 // It represents either a key-value pair, or an error. 541 type LabelOrError struct { 542 Key, Value string 543 Error error 544 } 545 546 // OrderedLabels streams the list of 'label_key="label_value"' in sorted order, except "le" which is 547 // a reserved Prometheus label name and should go last. 548 // If an error is encountered, it is returned as the Error field of LabelOrError, and no further 549 // messages will be sent on the channel. 550 func OrderedLabels(labels ...map[string]string) <-chan LabelOrError { 551 // This function is quite hot on the metric-rendering path, and its naive "just put all the 552 // strings in one map to ensure no dupes it, then in one slice and sort it" approach is very 553 // allocation-heavy. This approach is more computation-heavy (it runs in 554 // O(len(labels) * len(largest label map))), but the only heap allocations it does is for the 555 // following tiny slices and channels. In practice, the number of label maps and the size of 556 // each label map is tiny, so this is worth doing despite the theoretically-longer run time. 557 558 // Initialize the channels we'll use. 559 mapChannels := make([]chan keyVal, 0, len(labels)) 560 lastKeyVal := make([]keyVal, len(labels)) 561 resultCh := make(chan LabelOrError) 562 var cancelCh chan struct{} 563 // outputError is a helper function for when we have encountered an error mid-way. 564 outputError := func(err error) { 565 if cancelCh != nil { 566 for range mapChannels { 567 cancelCh <- struct{}{} 568 } 569 close(cancelCh) 570 } 571 resultCh <- LabelOrError{Error: err} 572 close(resultCh) 573 } 574 575 // Verify that no label is the empty string. It's not a valid label name, 576 // and we use the empty string later on in the function as a marker of having 577 // finished processing all labels from a given label map. 578 for _, labelMap := range labels { 579 for label := range labelMap { 580 if label == "" { 581 go outputError(errors.New("got empty-string label")) 582 return resultCh 583 } 584 } 585 } 586 587 // Each label map is processed in its own goroutine, 588 // which will stream it back to this function in sorted order. 589 cancelCh = make(chan struct{}, len(labels)) 590 for _, labelMap := range labels { 591 ch := make(chan keyVal) 592 mapChannels = append(mapChannels, ch) 593 go sortedIterateLabels(labelMap, ch, cancelCh) 594 } 595 596 // This goroutine is the meat of this function; it iterates through 597 // the results being streamed from each `sortedIterateLabels` goroutine 598 // that we spawned earlier, until all of them are exhausted or until we 599 // hit an error. 600 go func() { 601 // The "le" label is special and goes last, not in sorted order. 602 // gotLe is the empty string if there is no "le" label, 603 // otherwise it's the value of the "le" label. 604 var gotLe string 605 606 // numChannelsLeft tracks the number of channels that are still live. 607 for numChannelsLeft := len(mapChannels); numChannelsLeft > 0; { 608 // Iterate over all channels and ensure we have the freshest (smallest) 609 // label from each of them. 610 for i, ch := range mapChannels { 611 // A nil channel is one that has been closed. 612 if ch == nil { 613 continue 614 } 615 // If we already have the latest value from this channel, 616 // keep it there instead of getting a new one, 617 if lastKeyVal[i].Key != "" { 618 continue 619 } 620 // Otherwise, get a new label. 621 kv, open := <-ch 622 if !open { 623 // Channel has been closed, no more to read from this one. 624 numChannelsLeft-- 625 mapChannels[i] = nil 626 continue 627 } 628 if kv.Key == "le" { 629 if gotLe != "" { 630 outputError(errors.New("got duplicate 'le' label")) 631 return 632 } 633 gotLe = kv.Value 634 continue 635 } 636 lastKeyVal[i] = kv 637 } 638 639 // We have one key-value pair from each still-active channel now. 640 // Find the smallest one between them. 641 smallestKey := "" 642 indexForSmallest := -1 643 for i, kv := range lastKeyVal { 644 if kv.Key == "" { 645 continue 646 } 647 if smallestKey == "" || kv.Key < smallestKey { 648 smallestKey = kv.Key 649 indexForSmallest = i 650 } else if kv.Key == smallestKey { 651 outputError(fmt.Errorf("got duplicate label %q", smallestKey)) 652 return 653 } 654 } 655 656 if indexForSmallest == -1 { 657 // There are no more key-value pairs to output. We're done. 658 break 659 } 660 661 // Output the smallest key-value pairs out of all the channels. 662 resultCh <- LabelOrError{ 663 Key: smallestKey, 664 Value: lastKeyVal[indexForSmallest].Value, 665 } 666 // Mark the last key-value pair from the channel that gave us the 667 // smallest key-value pair as no longer present, so that we get a new 668 // key-value pair from it in the next iteration. 669 lastKeyVal[indexForSmallest] = keyVal{} 670 } 671 672 // Output the "le" label last. 673 if gotLe != "" { 674 resultCh <- LabelOrError{ 675 Key: "le", 676 Value: gotLe, 677 } 678 } 679 close(resultCh) 680 close(cancelCh) 681 }() 682 683 return resultCh 684 } 685 686 // writeLabelsTo writes a set of metric labels. 687 func (d *Data) writeLabelsTo(w io.Writer, extraLabels map[string]string, leLabel *Number) error { 688 if len(d.Labels)+len(d.ExternalLabels)+len(extraLabels) != 0 || leLabel != nil { 689 if _, err := io.WriteString(w, "{"); err != nil { 690 return err 691 } 692 var orderedLabels <-chan LabelOrError 693 if leLabel != nil { 694 orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels, map[string]string{"le": leLabel.String()}) 695 } else { 696 orderedLabels = OrderedLabels(d.Labels, d.ExternalLabels, extraLabels) 697 } 698 firstLabel := true 699 var foundError error 700 for labelOrError := range orderedLabels { 701 if foundError != nil { 702 continue 703 } 704 if labelOrError.Error != nil { 705 foundError = labelOrError.Error 706 continue 707 } 708 if !firstLabel { 709 if _, err := io.WriteString(w, ","); err != nil { 710 return err 711 } 712 } 713 firstLabel = false 714 if _, err := io.WriteString(w, labelOrError.Key); err != nil { 715 return err 716 } 717 if _, err := io.WriteString(w, "="); err != nil { 718 return err 719 } 720 if _, err := writeEscapedString(w, labelOrError.Value, true); err != nil { 721 return err 722 } 723 } 724 if foundError != nil { 725 return foundError 726 } 727 if _, err := io.WriteString(w, "}"); err != nil { 728 return err 729 } 730 } 731 return nil 732 } 733 734 // writeMetricLine writes a single line with a single number (val) to w. 735 func (d *Data) writeMetricLine(w io.Writer, metricSuffix string, val *Number, when time.Time, options SnapshotExportOptions, leLabel *Number, metricsWritten map[string]bool) error { 736 if err := d.writeMetricPreambleTo(w, options, metricsWritten); err != nil { 737 return err 738 } 739 if metricSuffix != "" { 740 if _, err := io.WriteString(w, metricSuffix); err != nil { 741 return err 742 } 743 } 744 if err := d.writeLabelsTo(w, options.ExtraLabels, leLabel); err != nil { 745 return err 746 } 747 if _, err := io.WriteString(w, " "); err != nil { 748 return err 749 } 750 if err := val.writeTo(w); err != nil { 751 return err 752 } 753 if _, err := io.WriteString(w, " "); err != nil { 754 return err 755 } 756 if _, err := writeInteger(w, when.UnixMilli()); err != nil { 757 return err 758 } 759 if _, err := io.WriteString(w, "\n"); err != nil { 760 return err 761 } 762 return nil 763 } 764 765 // writeTo writes the Data to the given writer in Prometheus format. 766 func (d *Data) writeTo(w io.Writer, when time.Time, options SnapshotExportOptions, metricsWritten map[string]bool) error { 767 switch d.Metric.Type { 768 case TypeUntyped, TypeGauge, TypeCounter: 769 return d.writeMetricLine(w, "", d.Number, when, options, nil, metricsWritten) 770 case TypeHistogram: 771 // Write an empty line before and after histograms to easily distinguish them from 772 // other metric lines. 773 if _, err := io.WriteString(w, "\n"); err != nil { 774 return err 775 } 776 var numSamples uint64 777 var samples Number 778 for _, bucket := range d.HistogramValue.Buckets { 779 numSamples += bucket.Samples 780 samples.Int = int64(numSamples) // Prometheus distribution bucket counts are cumulative. 781 if err := d.writeMetricLine(w, "_bucket", &samples, when, options, &bucket.UpperBound, metricsWritten); err != nil { 782 return err 783 } 784 } 785 if err := d.writeMetricLine(w, "_sum", &d.HistogramValue.Total, when, options, nil, metricsWritten); err != nil { 786 return err 787 } 788 samples.Int = int64(numSamples) 789 if err := d.writeMetricLine(w, "_count", &samples, when, options, nil, metricsWritten); err != nil { 790 return err 791 } 792 if err := d.writeMetricLine(w, "_min", &d.HistogramValue.Min, when, options, nil, metricsWritten); err != nil { 793 return err 794 } 795 if err := d.writeMetricLine(w, "_max", &d.HistogramValue.Max, when, options, nil, metricsWritten); err != nil { 796 return err 797 } 798 if err := d.writeMetricLine(w, "_ssd", &d.HistogramValue.SumOfSquaredDeviations, when, options, nil, metricsWritten); err != nil { 799 return err 800 } 801 // Empty line after the histogram. 802 if _, err := io.WriteString(w, "\n"); err != nil { 803 return err 804 } 805 return nil 806 default: 807 return fmt.Errorf("unknown metric type for metric %s: %v", d.Metric.Name, d.Metric.Type) 808 } 809 } 810 811 // Snapshot is a snapshot of the values of all the metrics at a certain point in time. 812 type Snapshot struct { 813 // When is the timestamp at which the snapshot was taken. 814 // Note that Prometheus ultimately encodes timestamps as millisecond-precision int64s from epoch. 815 When time.Time `json:"when,omitempty"` 816 817 // Data is the whole snapshot data. 818 // Each Data must be a unique combination of (Metric, Labels) within a Snapshot. 819 Data []*Data `json:"data,omitempty"` 820 } 821 822 // NewSnapshot returns a new Snapshot at the current time. 823 func NewSnapshot() *Snapshot { 824 return &Snapshot{When: timeNow()} 825 } 826 827 // Add data point(s) to the snapshot. 828 // Returns itself for chainability. 829 func (s *Snapshot) Add(data ...*Data) *Snapshot { 830 s.Data = append(s.Data, data...) 831 return s 832 } 833 834 // countingWriter implements io.Writer, and counts the number of bytes written to it. 835 // Useful in this file to keep track of total number of bytes without having to plumb this 836 // everywhere in the writeX() functions in this file. 837 type countingWriter struct { 838 w *bufio.Writer 839 written int 840 } 841 842 // Write implements io.Writer.Write. 843 func (w *countingWriter) Write(b []byte) (int, error) { 844 written, err := w.w.Write(b) 845 w.written += written 846 return written, err 847 } 848 849 // WriteString implements io.StringWriter.WriteString. 850 // This avoids going into the slow, allocation-heavy path of io.WriteString. 851 func (w *countingWriter) WriteString(s string) (int, error) { 852 written, err := w.w.WriteString(s) 853 w.written += written 854 return written, err 855 } 856 857 // Written returns the number of bytes written to the underlying writer (minus buffered writes). 858 func (w *countingWriter) Written() int { 859 return w.written - w.w.Buffered() 860 } 861 862 // writeSingleMetric writes the data to the given writer in Prometheus format. 863 // It returns the number of bytes written. 864 func (s *Snapshot) writeSingleMetric(w io.Writer, options SnapshotExportOptions, metricName string, metricsWritten map[string]bool) error { 865 if !strings.HasPrefix(metricName, options.ExporterPrefix) { 866 return nil 867 } 868 wantMetricName := strings.TrimPrefix(metricName, options.ExporterPrefix) 869 for _, d := range s.Data { 870 if d.Metric.Name != wantMetricName { 871 continue 872 } 873 if err := d.writeTo(w, s.When, options, metricsWritten); err != nil { 874 return err 875 } 876 } 877 return nil 878 } 879 880 // Write writes one or more snapshots to the writer. 881 // This ensures same-name metrics across different snapshots are printed together, per spec. 882 func Write(w io.Writer, options ExportOptions, snapshotsToOptions map[*Snapshot]SnapshotExportOptions) (int, error) { 883 if len(snapshotsToOptions) == 0 { 884 return 0, nil 885 } 886 cw := &countingWriter{w: bufio.NewWriter(w)} 887 if options.CommentHeader != "" { 888 for _, commentLine := range strings.Split(options.CommentHeader, "\n") { 889 if _, err := io.WriteString(cw, "# "); err != nil { 890 return cw.Written(), err 891 } 892 if _, err := io.WriteString(cw, commentLine); err != nil { 893 return cw.Written(), err 894 } 895 if _, err := io.WriteString(cw, "\n"); err != nil { 896 return cw.Written(), err 897 } 898 } 899 } 900 snapshots := make([]*Snapshot, 0, len(snapshotsToOptions)) 901 for snapshot := range snapshotsToOptions { 902 snapshots = append(snapshots, snapshot) 903 } 904 switch len(snapshots) { 905 case 1: // Single-snapshot case. 906 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 { 907 return cw.Written(), err 908 } 909 default: // Multi-snapshot case. 910 // Provide a consistent ordering of snapshots. 911 sort.Slice(snapshots, func(i, j int) bool { 912 return reflect.ValueOf(snapshots[i]).Pointer() < reflect.ValueOf(snapshots[j]).Pointer() 913 }) 914 if _, err := io.WriteString(cw, fmt.Sprintf("# Writing data from %d snapshots:\n", len(snapshots))); err != nil { 915 return cw.Written(), err 916 } 917 for _, snapshot := range snapshots { 918 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 { 919 return cw.Written(), err 920 } 921 } 922 } 923 if _, err := io.WriteString(cw, "\n"); err != nil { 924 return cw.Written(), err 925 } 926 if options.MetricsWritten == nil { 927 options.MetricsWritten = make(map[string]bool) 928 } 929 metricNamesMap := make(map[string]bool, len(options.MetricsWritten)) 930 metricNames := make([]string, 0, len(options.MetricsWritten)) 931 for _, snapshot := range snapshots { 932 for _, data := range snapshot.Data { 933 metricName := snapshotsToOptions[snapshot].ExporterPrefix + data.Metric.Name 934 if !metricNamesMap[metricName] { 935 metricNamesMap[metricName] = true 936 metricNames = append(metricNames, metricName) 937 } 938 } 939 } 940 sort.Strings(metricNames) 941 for _, metricName := range metricNames { 942 for _, snapshot := range snapshots { 943 snapshot.writeSingleMetric(cw, snapshotsToOptions[snapshot], metricName, options.MetricsWritten) 944 } 945 } 946 if _, err := io.WriteString(cw, "\n# End of metric data.\n"); err != nil { 947 return cw.Written(), err 948 } 949 if err := cw.w.Flush(); err != nil { 950 return cw.Written(), err 951 } 952 return cw.Written(), nil 953 }