You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

143 regels
4.7 KiB

  1. // Copyright 2021 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. //go:build go1.17
  14. // +build go1.17
  15. package internal
  16. import (
  17. "math"
  18. "path"
  19. "runtime/metrics"
  20. "strings"
  21. "github.com/prometheus/common/model"
  22. )
  23. // RuntimeMetricsToProm produces a Prometheus metric name from a runtime/metrics
  24. // metric description and validates whether the metric is suitable for integration
  25. // with Prometheus.
  26. //
  27. // Returns false if a name could not be produced, or if Prometheus does not understand
  28. // the runtime/metrics Kind.
  29. //
  30. // Note that the main reason a name couldn't be produced is if the runtime/metrics
  31. // package exports a name with characters outside the valid Prometheus metric name
  32. // character set. This is theoretically possible, but should never happen in practice.
  33. // Still, don't rely on it.
  34. func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool) {
  35. namespace := "go"
  36. comp := strings.SplitN(d.Name, ":", 2)
  37. key := comp[0]
  38. unit := comp[1]
  39. // The last path element in the key is the name,
  40. // the rest is the subsystem.
  41. subsystem := path.Dir(key[1:] /* remove leading / */)
  42. name := path.Base(key)
  43. // subsystem is translated by replacing all / and - with _.
  44. subsystem = strings.ReplaceAll(subsystem, "/", "_")
  45. subsystem = strings.ReplaceAll(subsystem, "-", "_")
  46. // unit is translated assuming that the unit contains no
  47. // non-ASCII characters.
  48. unit = strings.ReplaceAll(unit, "-", "_")
  49. unit = strings.ReplaceAll(unit, "*", "_")
  50. unit = strings.ReplaceAll(unit, "/", "_per_")
  51. // name has - replaced with _ and is concatenated with the unit and
  52. // other data.
  53. name = strings.ReplaceAll(name, "-", "_")
  54. name = name + "_" + unit
  55. if d.Cumulative {
  56. name = name + "_total"
  57. }
  58. valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name))
  59. switch d.Kind {
  60. case metrics.KindUint64:
  61. case metrics.KindFloat64:
  62. case metrics.KindFloat64Histogram:
  63. default:
  64. valid = false
  65. }
  66. return namespace, subsystem, name, valid
  67. }
  68. // RuntimeMetricsBucketsForUnit takes a set of buckets obtained for a runtime/metrics histogram
  69. // type (so, lower-bound inclusive) and a unit from a runtime/metrics name, and produces
  70. // a reduced set of buckets. This function always removes any -Inf bucket as it's represented
  71. // as the bottom-most upper-bound inclusive bucket in Prometheus.
  72. func RuntimeMetricsBucketsForUnit(buckets []float64, unit string) []float64 {
  73. switch unit {
  74. case "bytes":
  75. // Rebucket as powers of 2.
  76. return rebucketExp(buckets, 2)
  77. case "seconds":
  78. // Rebucket as powers of 10 and then merge all buckets greater
  79. // than 1 second into the +Inf bucket.
  80. b := rebucketExp(buckets, 10)
  81. for i := range b {
  82. if b[i] <= 1 {
  83. continue
  84. }
  85. b[i] = math.Inf(1)
  86. b = b[:i+1]
  87. break
  88. }
  89. return b
  90. }
  91. return buckets
  92. }
  93. // rebucketExp takes a list of bucket boundaries (lower bound inclusive) and
  94. // downsamples the buckets to those a multiple of base apart. The end result
  95. // is a roughly exponential (in many cases, perfectly exponential) bucketing
  96. // scheme.
  97. func rebucketExp(buckets []float64, base float64) []float64 {
  98. bucket := buckets[0]
  99. var newBuckets []float64
  100. // We may see a -Inf here, in which case, add it and skip it
  101. // since we risk producing NaNs otherwise.
  102. //
  103. // We need to preserve -Inf values to maintain runtime/metrics
  104. // conventions. We'll strip it out later.
  105. if bucket == math.Inf(-1) {
  106. newBuckets = append(newBuckets, bucket)
  107. buckets = buckets[1:]
  108. bucket = buckets[0]
  109. }
  110. // From now on, bucket should always have a non-Inf value because
  111. // Infs are only ever at the ends of the bucket lists, so
  112. // arithmetic operations on it are non-NaN.
  113. for i := 1; i < len(buckets); i++ {
  114. if bucket >= 0 && buckets[i] < bucket*base {
  115. // The next bucket we want to include is at least bucket*base.
  116. continue
  117. } else if bucket < 0 && buckets[i] < bucket/base {
  118. // In this case the bucket we're targeting is negative, and since
  119. // we're ascending through buckets here, we need to divide to get
  120. // closer to zero exponentially.
  121. continue
  122. }
  123. // The +Inf bucket will always be the last one, and we'll always
  124. // end up including it here because bucket
  125. newBuckets = append(newBuckets, bucket)
  126. bucket = buckets[i]
  127. }
  128. return append(newBuckets, bucket)
  129. }